booking-agent-cli 0.2.0__tar.gz → 0.3.0__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 (220) hide show
  1. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/PKG-INFO +8 -1
  2. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/README.md +6 -0
  3. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/pyproject.toml +3 -1
  4. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/__init__.py +1 -1
  5. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/SKILL.md +22 -12
  6. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/__init__.py +1 -1
  7. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/commands/flow.py +23 -0
  8. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/commands/update.py +54 -16
  9. booking_agent_cli-0.3.0/src/booking_agent_cli/install_integrity.py +68 -0
  10. booking_agent_cli-0.3.0/src/booking_agent_cli/provenance.py +328 -0
  11. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/session.py +13 -0
  12. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/support_report.py +1 -1
  13. booking_agent_cli-0.3.0/tests/integration/test_install_integrity.py +145 -0
  14. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_update_command.py +56 -3
  15. booking_agent_cli-0.3.0/tests/support/fixtures/provenance/pypi_attestations-0.0.30-py3-none-any.whl.provenance.json +66 -0
  16. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_check_version.py +50 -0
  17. booking_agent_cli-0.3.0/tests/unit/test_provenance.py +341 -0
  18. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/.gitignore +0 -0
  19. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/authorization.py +0 -0
  20. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/backend/__init__.py +0 -0
  21. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/backend/live.py +0 -0
  22. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/calendar/__init__.py +0 -0
  23. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/calendar/filter.py +0 -0
  24. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/calendar/google.py +0 -0
  25. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/calendar/oauth_listener.py +0 -0
  26. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/calendar/scope.py +0 -0
  27. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/circuit_breaker.py +0 -0
  28. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/config.py +0 -0
  29. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/defaults.env +0 -0
  30. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/discovery.py +0 -0
  31. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/headless.py +0 -0
  32. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/http_deadlines.py +0 -0
  33. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/http_retry.py +0 -0
  34. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/http_trace.py +0 -0
  35. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/merchant_pacing.py +0 -0
  36. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/models/__init__.py +0 -0
  37. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/models/platform.py +0 -0
  38. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/models/wire.py +0 -0
  39. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/namespace.py +0 -0
  40. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/observability.py +0 -0
  41. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/platform_profile.py +0 -0
  42. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/pop.py +0 -0
  43. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/profile_cache.py +0 -0
  44. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/registry_url.py +0 -0
  45. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/rest_client.py +0 -0
  46. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/secure_file.py +0 -0
  47. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/settings.py +0 -0
  48. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/signals.py +0 -0
  49. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/step_timing.py +0 -0
  50. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/ucp_capabilities.py +0 -0
  51. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/ucp_checkout.py +0 -0
  52. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/ucp_negotiation.py +0 -0
  53. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/ucp_profile.py +0 -0
  54. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent/usp_methods.py +0 -0
  55. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/activity_log.py +0 -0
  56. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/booking_poll.py +0 -0
  57. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/booking_summary.py +0 -0
  58. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/calendar_connection.py +0 -0
  59. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/calendar_event.py +0 -0
  60. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/calendar_preference.py +0 -0
  61. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/catalog.py +0 -0
  62. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/checkout_prereq.py +0 -0
  63. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/commands/__init__.py +0 -0
  64. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/commands/config.py +0 -0
  65. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/commands/lowlevel.py +0 -0
  66. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/commands/runtime.py +0 -0
  67. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/config_loader.py +0 -0
  68. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/engine.py +0 -0
  69. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/flow_timings.py +0 -0
  70. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/geo_eligibility.py +0 -0
  71. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/geocode.py +0 -0
  72. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/identity.py +0 -0
  73. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/link_cli.py +0 -0
  74. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/main.py +0 -0
  75. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/output.py +0 -0
  76. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/payment_helpers.py +0 -0
  77. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/payment_token.py +0 -0
  78. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/reschedule.py +0 -0
  79. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/runtime.py +0 -0
  80. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/runtime_prepare.py +0 -0
  81. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/search_area.py +0 -0
  82. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/skill_bundle.py +0 -0
  83. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/support_bundle_schema.py +0 -0
  84. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/timezone_context.py +0 -0
  85. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/src/booking_agent_cli/updates.py +0 -0
  86. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/.env.tests +0 -0
  87. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/conftest.py +0 -0
  88. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/__init__.py +0 -0
  89. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
  90. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/test_latency_soak.py +0 -0
  91. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/test_staging_registry_integration.py +0 -0
  92. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/test_timezone_booking_e2e.py +0 -0
  93. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/test_ucp_native_e2e.py +0 -0
  94. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/test_ucp_skill_flow_e2e.py +0 -0
  95. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/e2e/ucp_native.py +0 -0
  96. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/__init__.py +0 -0
  97. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_activity_log.py +0 -0
  98. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_at_rest_hygiene.py +0 -0
  99. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_booking_rejection.py +0 -0
  100. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_bulk_cancel.py +0 -0
  101. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_calendar_connection.py +0 -0
  102. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_calendar_connector.py +0 -0
  103. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_calendar_flow.py +0 -0
  104. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_calendar_preference.py +0 -0
  105. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_checkout_signals.py +0 -0
  106. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_circuit_breaker.py +0 -0
  107. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_config.py +0 -0
  108. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_correlation_flow.py +0 -0
  109. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_declined_spt.py +0 -0
  110. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_discover_registry_rejection.py +0 -0
  111. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_flow_evidence.py +0 -0
  112. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_flow_gates.py +0 -0
  113. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_flow_reschedule.py +0 -0
  114. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_flow_step_timings.py +0 -0
  115. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_google_calendar_oauth.py +0 -0
  116. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_home_visit_address.py +0 -0
  117. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_http_trace.py +0 -0
  118. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_identity_logout.py +0 -0
  119. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_lazy_oauth.py +0 -0
  120. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_link_auth.py +0 -0
  121. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_link_cli_spt.py +0 -0
  122. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_namespace.py +0 -0
  123. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_oauth_listener.py +0 -0
  124. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_oauth_metadata.py +0 -0
  125. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_platform_key_pop.py +0 -0
  126. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_platform_profile.py +0 -0
  127. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_pop_keystore.py +0 -0
  128. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_profile_cache.py +0 -0
  129. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_release_hold_uses_delete.py +0 -0
  130. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_request_retry_semantics.py +0 -0
  131. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_runtime_prepare.py +0 -0
  132. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_search_area.py +0 -0
  133. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_session_compat.py +0 -0
  134. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_session_retention.py +0 -0
  135. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_skill_portability.py +0 -0
  136. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_support_report.py +0 -0
  137. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_timezone_context.py +0 -0
  138. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_ucp_cancel_result.py +0 -0
  139. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_ucp_checkout_pop.py +0 -0
  140. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
  141. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_ucp_checkout_validate.py +0 -0
  142. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_ucp_profile.py +0 -0
  143. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/integration/test_wheel_contents.py +0 -0
  144. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/SOURCE +0 -0
  145. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/acp_booking_extension.json +0 -0
  146. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/availability.json +0 -0
  147. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/booking.json +0 -0
  148. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/calendar_freebusy.json +0 -0
  149. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/catalog.json +0 -0
  150. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/paid_bookings.json +0 -0
  151. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/profile.json +0 -0
  152. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/registry.json +0 -0
  153. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/rest_common.json +0 -0
  154. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/usp.json +0 -0
  155. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/waitlist.json +0 -0
  156. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/spec_schemas/webhook_event.json +0 -0
  157. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/__init__.py +0 -0
  158. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/fixtures/__init__.py +0 -0
  159. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/fixtures/profiles.py +0 -0
  160. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/fixtures/services.py +0 -0
  161. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/__init__.py +0 -0
  162. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/backend.py +0 -0
  163. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/link_cli.py +0 -0
  164. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/mock_calendar.py +0 -0
  165. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/pop_business.py +0 -0
  166. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/pop_transport.py +0 -0
  167. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/ucp_checkout_store.py +0 -0
  168. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/mock/ucp_server.py +0 -0
  169. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/offline_stack/__init__.py +0 -0
  170. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/support/offline_stack/stack.py +0 -0
  171. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/__init__.py +0 -0
  172. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_authorization_policy.py +0 -0
  173. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_booking_poll_credentials.py +0 -0
  174. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_booking_request_error.py +0 -0
  175. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_calendar_filter.py +0 -0
  176. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_catalog.py +0 -0
  177. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_conditional_writes.py +0 -0
  178. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_delivery_address.py +0 -0
  179. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_discovery.py +0 -0
  180. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_geo_eligibility.py +0 -0
  181. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_geocode.py +0 -0
  182. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_headless.py +0 -0
  183. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_http_deadlines.py +0 -0
  184. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_http_retry.py +0 -0
  185. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_live_search_registry.py +0 -0
  186. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_merchant_pacing.py +0 -0
  187. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_mock_support.py +0 -0
  188. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_money_path_events.py +0 -0
  189. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
  190. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_observability.py +0 -0
  191. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_outbound_member_names.py +0 -0
  192. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_package_layering.py +0 -0
  193. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_registry_search_filters.py +0 -0
  194. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_registry_search_pricing.py +0 -0
  195. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_registry_search_responses.py +0 -0
  196. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_registry_url.py +0 -0
  197. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_release_notes.py +0 -0
  198. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_rest_client.py +0 -0
  199. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_scoped_method_authorization.py +0 -0
  200. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_session_credentials.py +0 -0
  201. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_spec_conformance.py +0 -0
  202. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_step_timing.py +0 -0
  203. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_ucp_capabilities.py +0 -0
  204. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_ucp_complete_outcome.py +0 -0
  205. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_ucp_create_price_guard.py +0 -0
  206. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_ucp_negotiation.py +0 -0
  207. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_updates.py +0 -0
  208. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/unit/test_wire_model_conformance.py +0 -0
  209. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
  210. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
  211. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
  212. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
  213. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
  214. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/006-cross-key-replay.json +0 -0
  215. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
  216. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/008-replayed-jti.json +0 -0
  217. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/009-aud-mismatch.json +0 -0
  218. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/010-alg-none.json +0 -0
  219. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/README.md +0 -0
  220. {booking_agent_cli-0.2.0 → booking_agent_cli-0.3.0}/tests/vectors/pop/SOURCE +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: booking-agent-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: The Booking Agent CLI: a stateful USP booking flow with hard gates, plus its USP protocol library.
5
5
  Project-URL: Homepage, https://github.com/wix/booking-agent
6
6
  Project-URL: Documentation, https://github.com/wix/booking-agent/blob/master/booking-agent/README.md
@@ -27,6 +27,7 @@ Requires-Dist: click>=8.2
27
27
  Requires-Dist: cryptography>=42.0
28
28
  Requires-Dist: httpx>=0.27
29
29
  Requires-Dist: pydantic>=2.6
30
+ Requires-Dist: pypi-attestations>=0.0.30
30
31
  Requires-Dist: python-dotenv>=1.0
31
32
  Requires-Dist: rich>=13.7
32
33
  Requires-Dist: starlette>=0.37
@@ -63,6 +64,12 @@ pipx install booking-agent-cli
63
64
  booking-agent --help
64
65
  ```
65
66
 
67
+ Every release carries a PEP 740 attestation from the `wix/booking-agent` release
68
+ workflow. Check a downloaded wheel with
69
+ `uvx pypi-attestations verify pypi --repository https://github.com/wix/booking-agent <wheel>`.
70
+ `booking-agent update` installs only verified wheels, and the CLI checks its own installed
71
+ files against the attested release before it books or pays.
72
+
66
73
  Agents use the CLI through the Booking Agent skill:
67
74
  https://raw.githubusercontent.com/wix/booking-agent/master/booking-agent/skills/booking-agent/SKILL.md
68
75
 
@@ -22,6 +22,12 @@ pipx install booking-agent-cli
22
22
  booking-agent --help
23
23
  ```
24
24
 
25
+ Every release carries a PEP 740 attestation from the `wix/booking-agent` release
26
+ workflow. Check a downloaded wheel with
27
+ `uvx pypi-attestations verify pypi --repository https://github.com/wix/booking-agent <wheel>`.
28
+ `booking-agent update` installs only verified wheels, and the CLI checks its own installed
29
+ files against the attested release before it books or pays.
30
+
25
31
  Agents use the CLI through the Booking Agent skill:
26
32
  https://raw.githubusercontent.com/wix/booking-agent/master/booking-agent/skills/booking-agent/SKILL.md
27
33
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "booking-agent-cli"
3
- version = "0.2.0"
3
+ version = "0.3.0"
4
4
  description = "The Booking Agent CLI: a stateful USP booking flow with hard gates, plus its USP protocol library."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -28,6 +28,8 @@ dependencies = [
28
28
  "cryptography>=42.0",
29
29
  "httpx>=0.27",
30
30
  "pydantic>=2.6",
31
+ # PEP 740 provenance checks before an update installs and on first run (provenance.py).
32
+ "pypi-attestations>=0.0.30",
31
33
  "python-dotenv>=1.0",
32
34
  "rich>=13.7",
33
35
  "starlette>=0.37",
@@ -3,4 +3,4 @@
3
3
  Ships in the booking-agent-cli wheel and never imports ``booking_agent_cli``.
4
4
  """
5
5
 
6
- __version__ = "0.2.0"
6
+ __version__ = "0.3.0"
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 0.48.0
2
+ version: 0.49.0
3
3
  name: booking-agent
4
4
  description: |
5
5
  Books time-based services via USP from a Link wallet. Use when the user wants to book, schedule, reserve, or pay for a service from a business (haircut, lesson, repair, class, appointment, consult). The `booking-agent` CLI owns the booking state machine and refuses to proceed when human gates are skipped. Sign the user in to Link on your own, right after install and at the start of every booking (`booking-agent flow link-auth connect`, then start `booking-agent flow link-auth poll` in the background and post the sign-in URL in the same turn, exactly like `pay poll`); never wait for the user to ask to sign in or to say they approved. After `booking-agent flow business use`, follow `next_command` through `use-ucp-checkout-mode`; businesses without native checkout fail closed. Payment uses `booking-agent flow pay request` (default returns immediately with the approval URL), then start `booking-agent flow pay poll` in the background and post the approval URL to the user in the same turn; the poll finishes checkout once they approve. Confirm through `booking-agent flow book poll --wait`; never start an inbound webhook. Surface the approval URL exactly once. Personal identification (name, phone, email, address) must always come from the user's Link account via `link-cli`, never from chat or placeholders. Before any location-based search, the CLI proposes the Link shipping address and the user confirms it or gives another; online services skip this. Talk to the user in plain everyday language only; never expose CLI, UCP, auth, session, or other implementation details unless they explicitly ask.
@@ -18,8 +18,8 @@ metadata:
18
18
  - booking-agent
19
19
  - link-cli
20
20
  install:
21
- - kind: python
22
- package: booking-agent-cli
21
+ - kind: uv
22
+ package: "booking-agent-cli==0.3.0"
23
23
  bins: [booking-agent]
24
24
  user-invocable: true
25
25
  ---
@@ -208,18 +208,27 @@ Only then may you use terms like CLI, UCP, or session. Keep explanations brief.
208
208
 
209
209
  ## Installing
210
210
 
211
- Install the `booking-agent` CLI from PyPI (package `booking-agent-cli`) with either command:
211
+ Install the `booking-agent` CLI release this skill was written for (package `booking-agent-cli` on
212
+ PyPI). Every release is signed by the official release workflow (PEP 740), so check the signature
213
+ before installing:
212
214
 
213
215
  ```bash
214
- uv tool install booking-agent-cli
215
- # or
216
- pipx install booking-agent-cli
216
+ dir="$HOME/.booking-agent/verified-wheels"
217
+ uvx pip download "booking-agent-cli==0.3.0" --no-deps --only-binary :all: -d "$dir"
218
+ uvx pypi-attestations==0.0.30 verify pypi --repository https://github.com/wix/booking-agent \
219
+ "$dir/booking_agent_cli-0.3.0-py3-none-any.whl"
220
+ uv tool install --force "$dir/booking_agent_cli-0.3.0-py3-none-any.whl"
217
221
 
218
222
  booking-agent --help
219
223
  ```
220
224
 
225
+ Install only when the verify step passes. If `uvx` is not available, install the same pinned
226
+ release with `uv tool install booking-agent-cli==0.3.0` or `pipx install booking-agent-cli==0.3.0`;
227
+ the CLI checks its own files against the signed release on the first `booking-agent flow init`
228
+ and refuses to book or pay if they do not match.
229
+
221
230
  Harnesses that read the `install` block in this skill's metadata do this for you. If
222
- `booking-agent --help` still does not run, run one of the commands above yourself; if that is not
231
+ `booking-agent --help` still does not run, run the commands above yourself; if that is not
223
232
  possible in this runtime, ask the user to install the skill, then try again. To develop the CLI from
224
233
  a checkout instead, see the [Booking Agent README](https://github.com/wix/booking-agent/blob/master/booking-agent/README.md).
225
234
 
@@ -252,7 +261,7 @@ command. Do not tell the user about it. If it fails, continue the booking withou
252
261
 
253
262
  The CLI decides whether a newer version is worth mentioning; you never decide from version
254
263
  numbers or release pages. Pass this skill's version on every `flow init` (`--skill-version
255
- 0.48.0`, from the frontmatter above), so the CLI can tell when these instructions and the
264
+ 0.49.0`, from the frontmatter above), so the CLI can tell when these instructions and the
256
265
  installed CLI no longer match.
257
266
 
258
267
  - When a command returns `awaiting_user: update_consent`, ask the question in `prompt` in plain
@@ -482,7 +491,7 @@ have it). Do not invent a lookup.
482
491
  **Step 1b — Create a new session:**
483
492
 
484
493
  ```bash
485
- booking-agent flow init --user-request "<user's exact request>" --skill-version 0.48.0
494
+ booking-agent flow init --user-request "<user's exact request>" --skill-version 0.49.0
486
495
  ```
487
496
 
488
497
  Add `--capability <name>` once for each part of the request that clearly needs it: `calendar`
@@ -1129,7 +1138,7 @@ booking-agent flow calendar create-event
1129
1138
  Once the user has been told they are booked (and after Step 8 when it ran), run:
1130
1139
 
1131
1140
  ```bash
1132
- booking-agent update check --after-booking --skill-version 0.48.0
1141
+ booking-agent update check --after-booking --skill-version 0.49.0
1133
1142
  ```
1134
1143
 
1135
1144
  If it returns `awaiting_user: update_consent`, ask once, as in [Updates](#updates-only-with-the-users-yes).
@@ -1247,7 +1256,8 @@ recover.
1247
1256
  | `session_store_too_new` | Saved bookings on this device were written by a newer version of the assistant | Run `next_command` (`booking-agent update check`) and follow it; never delete or edit the saved state |
1248
1257
  | `update_not_confirmed` | `update` ran without `--user-confirmed` | Ask the user first; pass `--user-confirmed` only after they agree |
1249
1258
  | `update_session_in_progress` | `update` ran while a booking is unfinished | Finish or cancel the booking first, then offer the update again |
1250
- | `update_failed` / `update_not_verified` | The update did not install or did not start | Tell the user the update did not take; carry on with the installed version |
1259
+ | `update_failed` / `update_not_verified` | The update did not install, did not start, or could not be shown to come from the official release (`data.reason`); nothing unverified is installed | Tell the user the update did not take; carry on with the installed version |
1260
+ | `installation_unverified` | The installed CLI files do not match the signed official release, so it will not book or pay | Tell the user in plain words the assistant needs reinstalling; run `data.reinstall_command` only with their yes, then start the booking again. Never book another way |
1251
1261
  | `skill_output_refused` | `skill print --output` named a file that is not the loaded skill | Follow `data.skill_text` from `booking-agent skill print` instead |
1252
1262
 
1253
1263
  ### Notes to the assistant team
@@ -1,3 +1,3 @@
1
1
  """booking_agent_cli: typer-based stateful CLI for USP booking flows."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.3.0"
@@ -137,6 +137,7 @@ from ..runtime import (
137
137
  resolve_backend,
138
138
  resolve_calendar_service,
139
139
  )
140
+ from .. import install_integrity
140
141
  from .. import reschedule as reschedule_rules
141
142
  from .. import timezone_context as tz_context
142
143
  from ..activity_log import record_command
@@ -310,6 +311,19 @@ def _emit_booking_create_error(
310
311
  return typer.Exit(code=1)
311
312
 
312
313
 
314
+ def _require_verified_installation(store: Any, phase: str) -> None:
315
+ """Refuse to go on when the installed files do not match the attested release."""
316
+ failure = install_integrity.check(store)
317
+ if failure is not None:
318
+ emit_error(
319
+ phase,
320
+ code=install_integrity.CODE,
321
+ message=install_integrity.error_message(),
322
+ data=install_integrity.error_data(failure),
323
+ )
324
+ raise typer.Exit(code=1)
325
+
326
+
313
327
  # ---------------------------------------------------------------------------
314
328
  # init / status
315
329
  # ---------------------------------------------------------------------------
@@ -346,6 +360,8 @@ def init(
346
360
  )
347
361
  raise typer.Exit(code=1)
348
362
 
363
+ _require_verified_installation(store, "INIT")
364
+
349
365
  gate = _update_gate(user_request, skill_version, capability or [], allow_duplicate)
350
366
  if gate is not None:
351
367
  emit(gate)
@@ -4300,6 +4316,8 @@ def book(
4300
4316
  require_buyer_from_link_cli(session)
4301
4317
  except GateError as e:
4302
4318
  raise _handle_gate(session, e)
4319
+ if not dry_run:
4320
+ _require_verified_installation(load_store(), "BOOKED")
4303
4321
 
4304
4322
  backend = resolve_backend()
4305
4323
 
@@ -4533,6 +4551,11 @@ def book(
4533
4551
  pay_app = typer.Typer(help="Link spend approval and UCP checkout completion.")
4534
4552
  app.add_typer(pay_app, name="pay")
4535
4553
 
4554
+
4555
+ @pay_app.callback()
4556
+ def _pay_requires_verified_installation() -> None:
4557
+ _require_verified_installation(load_store(), "PAYMENT_PENDING")
4558
+
4536
4559
  _REBOOK_MSG = (
4537
4560
  "Run `booking-agent flow pay rebook --service-id <id>` to create a fresh checkout, "
4538
4561
  "then retry payment."
@@ -6,13 +6,14 @@ import os
6
6
  import shutil
7
7
  import subprocess
8
8
  import sys
9
+ import tempfile
9
10
  from pathlib import Path
10
11
  from typing import List, Optional, Tuple
11
12
 
12
13
  import typer
13
14
 
14
15
  from .. import __version__ as CLI_VERSION
15
- from .. import updates
16
+ from .. import provenance, updates
16
17
  from ..output import AgentOutput, emit, emit_error
17
18
  from ..session import (
18
19
  TERMINAL_PHASES,
@@ -31,6 +32,7 @@ DECLINE_COMMAND = "booking-agent update decline --prompt-key"
31
32
  SKILL_PRINT_COMMAND = "booking-agent skill print"
32
33
  INSTALL_TIMEOUT_SECONDS = 600
33
34
  VERIFY_TIMEOUT_SECONDS = 60
35
+ VERIFIED_WHEELS_DIR = "verified-wheels"
34
36
  _MAX_ANSWERED = 50
35
37
 
36
38
 
@@ -313,6 +315,22 @@ def update(
313
315
  ))
314
316
  return
315
317
 
318
+ try:
319
+ wheel = verified_wheel(target.cli_version)
320
+ except provenance.ProvenanceError as exc:
321
+ emit_error(
322
+ "UPDATE",
323
+ code="update_not_verified",
324
+ message=(
325
+ "The update could not be shown to come from the official Booking Agent "
326
+ "release, so nothing was installed. Tell the user in plain words that the "
327
+ "update was not installed, and carry on with the installed version."
328
+ ),
329
+ data={"target_version": target.cli_version, "reason": exc.reason, "detail": exc.message},
330
+ )
331
+ raise typer.Exit(code=1)
332
+
333
+ commands = install_commands(target.cli_version, newest=target.key == newest, wheel=wheel)
316
334
  ok, detail = _run(commands[installer], timeout=INSTALL_TIMEOUT_SECONDS)
317
335
  if not ok:
318
336
  emit_error(
@@ -340,6 +358,7 @@ def update(
340
358
  )
341
359
  raise typer.Exit(code=1)
342
360
 
361
+ _drop_other_wheels(wheel)
343
362
  _remember(store, f"release:{target.cli_version}")
344
363
  emit(AgentOutput(
345
364
  phase="UPDATE",
@@ -370,19 +389,31 @@ def _remember(store, key: Optional[str]) -> None:
370
389
 
371
390
 
372
391
  def is_editable_install() -> bool:
373
- try:
374
- from importlib.metadata import distribution
392
+ return provenance.install_source() == provenance.INSTALL_EDITABLE
375
393
 
376
- raw = distribution(PACKAGE).read_text("direct_url.json")
377
- except Exception:
378
- return False
379
- if not raw:
380
- return False
381
- try:
382
- info = json.loads(raw)
383
- except ValueError:
384
- return False
385
- return bool((info.get("dir_info") or {}).get("editable"))
394
+
395
+ def verified_wheel(version: str) -> Path:
396
+ """The PyPI wheel for ``version``, kept only once its PEP 740 provenance verifies.
397
+
398
+ It stays under the booking-agent home because uv and pipx record the file they
399
+ installed from.
400
+ """
401
+ from booking_agent import settings
402
+ from booking_agent.secure_file import ensure_private_dir
403
+
404
+ folder = settings.booking_agent_home() / VERIFIED_WHEELS_DIR
405
+ ensure_private_dir(folder)
406
+ with tempfile.TemporaryDirectory(dir=folder) as tmp:
407
+ downloaded = provenance.download_verified_wheel(version, Path(tmp))
408
+ wheel = folder / downloaded.name
409
+ os.replace(downloaded, wheel)
410
+ return wheel
411
+
412
+
413
+ def _drop_other_wheels(keep: Path) -> None:
414
+ for old in keep.parent.glob("*.whl"):
415
+ if old != keep:
416
+ old.unlink(missing_ok=True)
386
417
 
387
418
 
388
419
  def detect_installer() -> Optional[str]:
@@ -397,9 +428,16 @@ def detect_installer() -> Optional[str]:
397
428
  return None
398
429
 
399
430
 
400
- def install_commands(version: str, *, newest: bool) -> dict:
401
- # ">=" leaves later upgrades free; "==" holds back a newer release that is not ready.
402
- spec = f"{PACKAGE}>={version}" if newest else f"{PACKAGE}=={version}"
431
+ def install_commands(version: str, *, newest: bool, wheel: Optional[Path] = None) -> dict:
432
+ """Install the verified ``wheel`` itself, so nothing is resolved from the index again.
433
+
434
+ Without a wheel (commands shown to the user to run by hand): ">=" leaves later
435
+ upgrades free; "==" holds back a newer release that is not ready.
436
+ """
437
+ if wheel is not None:
438
+ spec = str(wheel)
439
+ else:
440
+ spec = f"{PACKAGE}>={version}" if newest else f"{PACKAGE}=={version}"
403
441
  return {
404
442
  "uv": ["uv", "tool", "install", "--force", spec],
405
443
  "pipx": ["pipx", "install", "--force", spec],
@@ -0,0 +1,68 @@
1
+ """Whether this installation may book and take payments: the first-run provenance check.
2
+
3
+ The first ``flow init`` after an install or update checks the installed files against
4
+ the attested release (``provenance.verify_installed``) and remembers the outcome for
5
+ that exact installation (its ``RECORD`` fingerprint). A mismatch is remembered too, so
6
+ ``flow book`` and every ``flow pay`` command refuse until the CLI is reinstalled. A
7
+ failure to reach PyPI decides nothing and is retried on the next check.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Dict, Optional
12
+
13
+ from . import __version__ as CLI_VERSION
14
+ from . import provenance
15
+
16
+ CODE = "installation_unverified"
17
+
18
+
19
+ def _same_install(record: Any, fingerprint: Optional[str]) -> bool:
20
+ return (
21
+ isinstance(record, dict)
22
+ and record.get("version") == CLI_VERSION
23
+ and record.get("record_sha256") == fingerprint
24
+ )
25
+
26
+
27
+ def check(store: Any) -> Optional[Dict[str, Any]]:
28
+ """The recorded failure for this installation, or None when it may proceed."""
29
+ from .session import save_store
30
+
31
+ fingerprint = provenance.installed_fingerprint()
32
+ if _same_install(store.install_integrity_failure, fingerprint):
33
+ return store.install_integrity_failure
34
+ if _same_install(store.install_verified, fingerprint):
35
+ return None
36
+ result = provenance.verify_installed()
37
+ if result.skipped is not None or result.transient:
38
+ return None
39
+ if result.ok:
40
+ store.install_verified = {"version": CLI_VERSION, "record_sha256": fingerprint}
41
+ store.install_integrity_failure = None
42
+ else:
43
+ store.install_integrity_failure = {
44
+ "version": CLI_VERSION,
45
+ "record_sha256": fingerprint,
46
+ "reason": result.reason,
47
+ "detail": result.message,
48
+ }
49
+ save_store(store)
50
+ return store.install_integrity_failure
51
+
52
+
53
+ def error_message() -> str:
54
+ return (
55
+ "The booking assistant installed on this device does not match the official "
56
+ "release, so it will not book or take payments. Tell the user in plain words that "
57
+ "the assistant needs to be reinstalled before it can book, and reinstall it only "
58
+ "with their yes, by running data.reinstall_command. Do not try to book another way."
59
+ )
60
+
61
+
62
+ def error_data(failure: Dict[str, Any]) -> Dict[str, Any]:
63
+ return {
64
+ "reason": failure.get("reason"),
65
+ "detail": failure.get("detail"),
66
+ "installed_version": CLI_VERSION,
67
+ "reinstall_command": provenance.reinstall_command(),
68
+ }