command_tower 0.4.0 → 0.10.0

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 (604) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +102 -29
  3. data/app/auth/command_tower/auth/auth_context.rb +36 -0
  4. data/app/auth/command_tower/auth/password_recovery_session_context.rb +11 -0
  5. data/app/auth/command_tower/auth/signup_session_context.rb +11 -0
  6. data/app/controllers/command_tower/admin/messaging/announcements_controller.rb +34 -0
  7. data/app/controllers/command_tower/application_controller.rb +80 -14
  8. data/app/controllers/command_tower/auth/email/availability_controller.rb +35 -0
  9. data/app/controllers/command_tower/auth/email_verification/send_controller.rb +24 -0
  10. data/app/controllers/command_tower/auth/email_verification/verify_controller.rb +41 -0
  11. data/app/controllers/command_tower/auth/identity_policy_controller.rb +14 -0
  12. data/app/controllers/command_tower/auth/logout_controller.rb +14 -0
  13. data/app/controllers/command_tower/auth/password_recovery_session/create_controller.rb +17 -0
  14. data/app/controllers/command_tower/auth/password_reset/reset_controller.rb +35 -0
  15. data/app/controllers/command_tower/auth/password_reset/send_controller.rb +40 -0
  16. data/app/controllers/command_tower/auth/password_reset/validate_controller.rb +35 -0
  17. data/app/controllers/command_tower/auth/plain_text/login_controller.rb +22 -0
  18. data/app/controllers/command_tower/auth/register_controller.rb +33 -0
  19. data/app/controllers/command_tower/auth/session_controller.rb +21 -0
  20. data/app/controllers/command_tower/auth/signup_session/create_controller.rb +17 -0
  21. data/app/controllers/command_tower/auth/username/availability_controller.rb +35 -0
  22. data/app/controllers/command_tower/me/inbox_controller.rb +85 -0
  23. data/app/controllers/command_tower/me/name_controller.rb +37 -0
  24. data/app/controllers/command_tower/me/password_controller.rb +37 -0
  25. data/app/controllers/command_tower/me/phone_controller.rb +44 -0
  26. data/app/controllers/command_tower/me/phone_verification/verifications_controller.rb +23 -0
  27. data/app/controllers/command_tower/me/phone_verification/verify_controller.rb +34 -0
  28. data/app/controllers/command_tower/me/preferences_controller.rb +46 -0
  29. data/app/controllers/command_tower/me/pushover/verifications_controller.rb +23 -0
  30. data/app/controllers/command_tower/me/pushover_controller.rb +66 -0
  31. data/app/controllers/command_tower/me_controller.rb +19 -0
  32. data/app/controllers/command_tower/profile_controller.rb +19 -0
  33. data/app/controllers/concerns/command_tower/api/application_response_renderer.rb +82 -0
  34. data/app/controllers/concerns/command_tower/api/deserializer_failure_details.rb +22 -0
  35. data/app/controllers/concerns/command_tower/auth/authentication_boundary.rb +34 -0
  36. data/app/controllers/concerns/command_tower/auth/authorization_boundary.rb +26 -0
  37. data/app/controllers/concerns/command_tower/auth/invalid_credentials_deserializer_renderer.rb +23 -0
  38. data/app/controllers/concerns/command_tower/auth/password_recovery_session_boundary.rb +29 -0
  39. data/app/controllers/concerns/command_tower/auth/signup_session_boundary.rb +29 -0
  40. data/app/deserializers/command_tower/deserializers/admin/messaging/create_announcement_deserializer.rb +106 -0
  41. data/app/deserializers/command_tower/deserializers/application_deserializer.rb +148 -0
  42. data/app/deserializers/command_tower/deserializers/auth/email_availability_deserializer.rb +19 -0
  43. data/app/deserializers/command_tower/deserializers/auth/email_verification/verify_deserializer.rb +21 -0
  44. data/app/deserializers/command_tower/deserializers/auth/password_reset/reset_deserializer.rb +31 -0
  45. data/app/deserializers/command_tower/deserializers/auth/password_reset/send_deserializer.rb +21 -0
  46. data/app/deserializers/command_tower/deserializers/auth/password_reset/validate_deserializer.rb +23 -0
  47. data/app/deserializers/command_tower/deserializers/auth/plain_text/login_deserializer.rb +24 -0
  48. data/app/deserializers/command_tower/deserializers/auth/register_deserializer.rb +42 -0
  49. data/app/deserializers/command_tower/deserializers/auth/username_availability_deserializer.rb +19 -0
  50. data/app/deserializers/command_tower/deserializers/clients/errors.rb +45 -0
  51. data/app/deserializers/command_tower/deserializers/clients/missing.rb +10 -0
  52. data/app/deserializers/command_tower/deserializers/clients/payload.rb +21 -0
  53. data/app/deserializers/command_tower/deserializers/clients/result.rb +122 -0
  54. data/app/deserializers/command_tower/deserializers/clients/types.rb +192 -0
  55. data/app/deserializers/command_tower/deserializers/coercion_result.rb +35 -0
  56. data/app/deserializers/command_tower/deserializers/failure.rb +14 -0
  57. data/app/deserializers/command_tower/deserializers/me/change_password_deserializer.rb +41 -0
  58. data/app/deserializers/command_tower/deserializers/me/phone_verification/verify_deserializer.rb +29 -0
  59. data/app/deserializers/command_tower/deserializers/me/pushover/credentials_deserializer.rb +31 -0
  60. data/app/deserializers/command_tower/deserializers/me/update_name_deserializer.rb +22 -0
  61. data/app/deserializers/command_tower/deserializers/me/update_phone_deserializer.rb +22 -0
  62. data/app/deserializers/command_tower/deserializers/messaging/inbox.rb +77 -0
  63. data/app/deserializers/command_tower/deserializers/messaging/preferences/show_deserializer.rb +17 -0
  64. data/app/deserializers/command_tower/deserializers/messaging/preferences/update_deserializer.rb +97 -0
  65. data/app/errors/command_tower/errors/account/phone_already_verified_error.rb +17 -0
  66. data/app/errors/command_tower/errors/account/phone_missing_error.rb +17 -0
  67. data/app/errors/command_tower/errors/account/phone_verification_code_invalid_error.rb +17 -0
  68. data/app/errors/command_tower/errors/account/phone_verification_expired_error.rb +17 -0
  69. data/app/errors/command_tower/errors/account/phone_verification_send_failed_error.rb +25 -0
  70. data/app/errors/command_tower/errors/account/phone_verification_stale_error.rb +17 -0
  71. data/app/errors/command_tower/errors/account/phone_verification_throttled_error.rb +24 -0
  72. data/app/errors/command_tower/errors/account/pushover_already_configured_error.rb +21 -0
  73. data/app/errors/command_tower/errors/account/pushover_capability_unavailable_error.rb +21 -0
  74. data/app/errors/command_tower/errors/account/pushover_not_configured_error.rb +21 -0
  75. data/app/errors/command_tower/errors/account/pushover_provider_unavailable_error.rb +21 -0
  76. data/app/errors/command_tower/errors/account/pushover_verification_failed_error.rb +27 -0
  77. data/app/errors/command_tower/errors/account/sms_capability_unavailable_error.rb +21 -0
  78. data/app/errors/command_tower/errors/application_error.rb +27 -0
  79. data/app/errors/command_tower/errors/auth/csrf_mismatch_error.rb +17 -0
  80. data/app/errors/command_tower/errors/auth/csrf_missing_error.rb +17 -0
  81. data/app/errors/command_tower/errors/auth/email_already_registered_error.rb +17 -0
  82. data/app/errors/command_tower/errors/auth/email_verification_required_error.rb +21 -0
  83. data/app/errors/command_tower/errors/auth/invalid_credentials_error.rb +21 -0
  84. data/app/errors/command_tower/errors/auth/password_recovery_ip_rate_limit_error.rb +25 -0
  85. data/app/errors/command_tower/errors/auth/password_recovery_session_expired_error.rb +21 -0
  86. data/app/errors/command_tower/errors/auth/password_recovery_session_invalid_error.rb +21 -0
  87. data/app/errors/command_tower/errors/auth/password_recovery_session_missing_error.rb +21 -0
  88. data/app/errors/command_tower/errors/auth/password_recovery_session_rate_limit_error.rb +25 -0
  89. data/app/errors/command_tower/errors/auth/password_reset_invalid_token_error.rb +21 -0
  90. data/app/errors/command_tower/errors/auth/password_reset_unavailable_error.rb +21 -0
  91. data/app/errors/command_tower/errors/auth/signup_ip_rate_limit_error.rb +25 -0
  92. data/app/errors/command_tower/errors/auth/signup_session_expired_error.rb +17 -0
  93. data/app/errors/command_tower/errors/auth/signup_session_invalid_error.rb +17 -0
  94. data/app/errors/command_tower/errors/auth/signup_session_missing_error.rb +17 -0
  95. data/app/errors/command_tower/errors/auth/signup_session_rate_limit_error.rb +25 -0
  96. data/app/errors/command_tower/errors/auth/verification_code_invalid_error.rb +17 -0
  97. data/app/errors/command_tower/errors/auth/verification_send_failed_error.rb +25 -0
  98. data/app/errors/command_tower/errors/forbidden_error.rb +19 -0
  99. data/app/errors/command_tower/errors/internal_error.rb +19 -0
  100. data/app/errors/command_tower/errors/messaging/accept_rejected_error.rb +21 -0
  101. data/app/errors/command_tower/errors/messaging/idempotency_conflict_error.rb +21 -0
  102. data/app/errors/command_tower/errors/messaging/recipient_unresolved_error.rb +21 -0
  103. data/app/errors/command_tower/errors/not_found_error.rb +19 -0
  104. data/app/errors/command_tower/errors/unauthorized_error.rb +19 -0
  105. data/app/errors/command_tower/errors/validation_error.rb +15 -0
  106. data/app/jobs/command_tower/messaging/channel_delivery_execution_job.rb +18 -0
  107. data/app/jobs/command_tower/messaging/communications/produce_recipient_job.rb +24 -0
  108. data/app/jobs/command_tower/messaging/execution_recovery_job.rb +13 -0
  109. data/app/jobs/command_tower/messaging/handoff_job.rb +15 -0
  110. data/app/jobs/command_tower/messaging/handoff_recovery_job.rb +13 -0
  111. data/app/mailers/command_tower/email_verification_mailer.rb +5 -3
  112. data/app/mailers/command_tower/messaging/channel_mailer.rb +25 -0
  113. data/app/mailers/command_tower/password_reset_mailer.rb +18 -0
  114. data/app/models/command_tower/messaging/channel_delivery.rb +45 -0
  115. data/app/models/command_tower/messaging/communication.rb +60 -0
  116. data/app/models/command_tower/messaging/delivery_attempt.rb +28 -0
  117. data/app/models/command_tower/messaging/destination_plan.rb +17 -0
  118. data/app/models/command_tower/messaging/endpoint.rb +98 -0
  119. data/app/models/command_tower/messaging/endpoint_pushover_credential.rb +18 -0
  120. data/app/models/command_tower/messaging/inbox_item.rb +52 -0
  121. data/app/models/command_tower/messaging/notification_preference.rb +21 -0
  122. data/app/models/user.rb +4 -1
  123. data/app/serializers/command_tower/serializers/admin/messaging/announcement_response_serializer.rb +41 -0
  124. data/app/serializers/command_tower/serializers/application/envelope_serializer.rb +25 -0
  125. data/app/serializers/command_tower/serializers/application/error_entry_serializer.rb +18 -0
  126. data/app/serializers/command_tower/serializers/application_serializer.rb +26 -0
  127. data/app/serializers/command_tower/serializers/auth/email_availability_serializer.rb +17 -0
  128. data/app/serializers/command_tower/serializers/auth/email_verification/message_response_serializer.rb +15 -0
  129. data/app/serializers/command_tower/serializers/auth/identity_policy_serializer.rb +36 -0
  130. data/app/serializers/command_tower/serializers/auth/login_response_serializer.rb +17 -0
  131. data/app/serializers/command_tower/serializers/auth/logout_response_serializer.rb +13 -0
  132. data/app/serializers/command_tower/serializers/auth/password_recovery_session_response_serializer.rb +16 -0
  133. data/app/serializers/command_tower/serializers/auth/password_reset/message_response_serializer.rb +15 -0
  134. data/app/serializers/command_tower/serializers/auth/password_reset/validate_response_serializer.rb +22 -0
  135. data/app/serializers/command_tower/serializers/auth/register_response_serializer.rb +16 -0
  136. data/app/serializers/command_tower/serializers/auth/session_response_serializer.rb +16 -0
  137. data/app/serializers/command_tower/serializers/auth/signup_session_response_serializer.rb +16 -0
  138. data/app/serializers/command_tower/serializers/auth/user_serializer.rb +21 -0
  139. data/app/serializers/command_tower/serializers/auth/username_availability_serializer.rb +17 -0
  140. data/app/serializers/command_tower/serializers/me/account_serializer.rb +45 -0
  141. data/app/serializers/command_tower/serializers/me/change_password_response_serializer.rb +13 -0
  142. data/app/serializers/command_tower/serializers/me/pushover_serializer.rb +59 -0
  143. data/app/serializers/command_tower/serializers/messaging/inbox.rb +45 -0
  144. data/app/serializers/command_tower/serializers/messaging/preferences/catalog_serializer.rb +17 -0
  145. data/app/serializers/command_tower/serializers/messaging/preferences/category_serializer.rb +23 -0
  146. data/app/serializers/command_tower/serializers/messaging/preferences/notification_serializer.rb +30 -0
  147. data/app/serializers/command_tower/serializers/profile/profile_serializer.rb +13 -0
  148. data/app/services/command_tower/README.md +22 -6
  149. data/app/services/command_tower/auth/request_context.rb +21 -0
  150. data/app/services/command_tower/authorize/validate.rb +54 -18
  151. data/app/services/command_tower/clients/client_base.rb +183 -0
  152. data/app/services/command_tower/clients/client_result.rb +45 -0
  153. data/app/services/command_tower/clients/const_resolution.rb +32 -0
  154. data/app/services/command_tower/clients/decoded_response.rb +16 -0
  155. data/app/services/command_tower/clients/endpoint_base.rb +242 -0
  156. data/app/services/command_tower/clients/endpoint_input.rb +18 -0
  157. data/app/services/command_tower/clients/errors/authentication_error.rb +26 -0
  158. data/app/services/command_tower/clients/errors/configuration_error.rb +10 -0
  159. data/app/services/command_tower/clients/errors/deserialization_error.rb +26 -0
  160. data/app/services/command_tower/clients/errors/discovery_error.rb +10 -0
  161. data/app/services/command_tower/clients/errors/upstream_error.rb +26 -0
  162. data/app/services/command_tower/clients/invocation.rb +61 -0
  163. data/app/services/command_tower/clients/namespace_proxy.rb +75 -0
  164. data/app/services/command_tower/clients/scoped_client.rb +88 -0
  165. data/app/services/command_tower/clients/serialized_request.rb +17 -0
  166. data/app/services/command_tower/clients/transport/error.rb +18 -0
  167. data/app/services/command_tower/clients/transport/faraday_adapter.rb +129 -0
  168. data/app/services/command_tower/clients/transport/faraday_config.rb +45 -0
  169. data/app/services/command_tower/clients/transport/request.rb +50 -0
  170. data/app/services/command_tower/clients/transport/response.rb +22 -0
  171. data/app/services/command_tower/clients/url.rb +56 -0
  172. data/app/services/command_tower/identity/phone/normalizer.rb +41 -0
  173. data/app/services/command_tower/identity/phone_verification/sms_configuration.rb +58 -0
  174. data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/fake_adapter.rb +35 -0
  175. data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/log_adapter.rb +34 -0
  176. data/app/services/command_tower/identity/phone_verification/sms_transport/adapters/twilio_adapter.rb +60 -0
  177. data/app/services/command_tower/identity/phone_verification/sms_transport.rb +42 -0
  178. data/app/services/command_tower/jwt/authenticate_user.rb +61 -34
  179. data/app/services/command_tower/jwt/authentication_outcome.rb +36 -0
  180. data/app/services/command_tower/jwt/decode.rb +27 -8
  181. data/app/services/command_tower/jwt/encode.rb +4 -6
  182. data/app/services/command_tower/jwt/login_create.rb +6 -7
  183. data/app/services/command_tower/login_strategy/plain_text/email_verification/required.rb +11 -9
  184. data/app/services/command_tower/messaging/accept/channel_delivery_result.rb +13 -0
  185. data/app/services/command_tower/messaging/accept/coordinator.rb +166 -0
  186. data/app/services/command_tower/messaging/accept/error.rb +9 -0
  187. data/app/services/command_tower/messaging/accept/excluded_destination.rb +24 -0
  188. data/app/services/command_tower/messaging/accept/handoff_scheduler.rb +40 -0
  189. data/app/services/command_tower/messaging/accept/idempotency_conflict_error.rb +9 -0
  190. data/app/services/command_tower/messaging/accept/illegal_override_error.rb +9 -0
  191. data/app/services/command_tower/messaging/accept/impossible_mandatory_plan_error.rb +9 -0
  192. data/app/services/command_tower/messaging/accept/invalid_preference_error.rb +9 -0
  193. data/app/services/command_tower/messaging/accept/invariant_error.rb +9 -0
  194. data/app/services/command_tower/messaging/accept/operation_logger.rb +97 -0
  195. data/app/services/command_tower/messaging/accept/persistence_error.rb +9 -0
  196. data/app/services/command_tower/messaging/accept/persister.rb +90 -0
  197. data/app/services/command_tower/messaging/accept/request_normalizer.rb +100 -0
  198. data/app/services/command_tower/messaging/accept/result.rb +99 -0
  199. data/app/services/command_tower/messaging/accept/unknown_type_error.rb +9 -0
  200. data/app/services/command_tower/messaging/accept/validation_error.rb +9 -0
  201. data/app/services/command_tower/messaging/channel_detectors.rb +52 -0
  202. data/app/services/command_tower/messaging/channels/definition.rb +24 -0
  203. data/app/services/command_tower/messaging/channels/error.rb +9 -0
  204. data/app/services/command_tower/messaging/channels/unknown_channel_error.rb +9 -0
  205. data/app/services/command_tower/messaging/channels.rb +85 -0
  206. data/app/services/command_tower/messaging/contract/communications.rb +29 -0
  207. data/app/services/command_tower/messaging/contract/error.rb +9 -0
  208. data/app/services/command_tower/messaging/contract/internal/finder.rb +43 -0
  209. data/app/services/command_tower/messaging/contract/invariant_error.rb +9 -0
  210. data/app/services/command_tower/messaging/contract/mappers/channel_delivery_mapper.rb +53 -0
  211. data/app/services/command_tower/messaging/contract/mappers/communication_mapper.rb +61 -0
  212. data/app/services/command_tower/messaging/contract/mappers/delivery_attempt_mapper.rb +27 -0
  213. data/app/services/command_tower/messaging/contract/mappers/destination_plan_mapper.rb +17 -0
  214. data/app/services/command_tower/messaging/contract/mappers/inbox_item_mapper.rb +17 -0
  215. data/app/services/command_tower/messaging/contract/not_found_error.rb +9 -0
  216. data/app/services/command_tower/messaging/contract/observability/correlation.rb +17 -0
  217. data/app/services/command_tower/messaging/contract/observability/operation_logger.rb +101 -0
  218. data/app/services/command_tower/messaging/contract/observability/structured_logger.rb +56 -0
  219. data/app/services/command_tower/messaging/contract/requests/find_communication.rb +21 -0
  220. data/app/services/command_tower/messaging/contract/results/channel_delivery_result.rb +25 -0
  221. data/app/services/command_tower/messaging/contract/results/communication_result.rb +22 -0
  222. data/app/services/command_tower/messaging/contract/results/delivery_attempt_result.rb +21 -0
  223. data/app/services/command_tower/messaging/contract/results/destination_plan_result.rb +11 -0
  224. data/app/services/command_tower/messaging/contract/results/inbox_item_result.rb +11 -0
  225. data/app/services/command_tower/messaging/contract/validation_error.rb +9 -0
  226. data/app/services/command_tower/messaging/delivery_retry_policy.rb +30 -0
  227. data/app/services/command_tower/messaging/endpoints/begin_verification.rb +13 -0
  228. data/app/services/command_tower/messaging/endpoints/channel_gate.rb +39 -0
  229. data/app/services/command_tower/messaging/endpoints/conflict_error.rb +9 -0
  230. data/app/services/command_tower/messaging/endpoints/create.rb +181 -0
  231. data/app/services/command_tower/messaging/endpoints/error.rb +9 -0
  232. data/app/services/command_tower/messaging/endpoints/fingerprinter.rb +50 -0
  233. data/app/services/command_tower/messaging/endpoints/invalid_transition_error.rb +9 -0
  234. data/app/services/command_tower/messaging/endpoints/lifecycle.rb +37 -0
  235. data/app/services/command_tower/messaging/endpoints/mark_invalid.rb +32 -0
  236. data/app/services/command_tower/messaging/endpoints/mark_verification_failed.rb +13 -0
  237. data/app/services/command_tower/messaging/endpoints/mark_verified.rb +13 -0
  238. data/app/services/command_tower/messaging/endpoints/not_found_error.rb +9 -0
  239. data/app/services/command_tower/messaging/endpoints/persistence.rb +91 -0
  240. data/app/services/command_tower/messaging/endpoints/pushover/verify.rb +101 -0
  241. data/app/services/command_tower/messaging/endpoints/query.rb +28 -0
  242. data/app/services/command_tower/messaging/endpoints/replace.rb +181 -0
  243. data/app/services/command_tower/messaging/endpoints/reset_verification.rb +13 -0
  244. data/app/services/command_tower/messaging/endpoints/revoke.rb +40 -0
  245. data/app/services/command_tower/messaging/endpoints/safe_view.rb +50 -0
  246. data/app/services/command_tower/messaging/endpoints/secret_box.rb +86 -0
  247. data/app/services/command_tower/messaging/endpoints/secret_reader.rb +72 -0
  248. data/app/services/command_tower/messaging/endpoints/validation_error.rb +9 -0
  249. data/app/services/command_tower/messaging/endpoints/validators/push.rb +24 -0
  250. data/app/services/command_tower/messaging/endpoints/validators/pushover.rb +68 -0
  251. data/app/services/command_tower/messaging/endpoints/validators.rb +36 -0
  252. data/app/services/command_tower/messaging/endpoints/verification.rb +39 -0
  253. data/app/services/command_tower/messaging/endpoints/verification_failed_error.rb +18 -0
  254. data/app/services/command_tower/messaging/endpoints/verification_transition.rb +20 -0
  255. data/app/services/command_tower/messaging/endpoints.rb +55 -0
  256. data/app/services/command_tower/messaging/execution/adapter_request.rb +45 -0
  257. data/app/services/command_tower/messaging/execution/adapter_result.rb +81 -0
  258. data/app/services/command_tower/messaging/execution/adapters/email/adapter.rb +78 -0
  259. data/app/services/command_tower/messaging/execution/adapters/email/configuration.rb +55 -0
  260. data/app/services/command_tower/messaging/execution/adapters/fake_adapter.rb +34 -0
  261. data/app/services/command_tower/messaging/execution/adapters/pushover/adapter.rb +107 -0
  262. data/app/services/command_tower/messaging/execution/adapters/pushover/configuration.rb +30 -0
  263. data/app/services/command_tower/messaging/execution/adapters/sms/adapter.rb +90 -0
  264. data/app/services/command_tower/messaging/execution/adapters/sms/configuration.rb +58 -0
  265. data/app/services/command_tower/messaging/execution/adapters/sms/log_adapter.rb +34 -0
  266. data/app/services/command_tower/messaging/execution/adapters/sms/twilio_http_client.rb +70 -0
  267. data/app/services/command_tower/messaging/execution/adapters/unconfigured_adapter.rb +20 -0
  268. data/app/services/command_tower/messaging/execution/attempt_ledger.rb +146 -0
  269. data/app/services/command_tower/messaging/execution/claim.rb +35 -0
  270. data/app/services/command_tower/messaging/execution/invalid_adapter_contract_error.rb +9 -0
  271. data/app/services/command_tower/messaging/execution/operation_logger.rb +237 -0
  272. data/app/services/command_tower/messaging/execution/readiness_terminal_mapping.rb +37 -0
  273. data/app/services/command_tower/messaging/execution/recovery.rb +78 -0
  274. data/app/services/command_tower/messaging/execution/resolve_recipient_address.rb +58 -0
  275. data/app/services/command_tower/messaging/execution/revalidate_readiness.rb +73 -0
  276. data/app/services/command_tower/messaging/execution/scheduler.rb +38 -0
  277. data/app/services/command_tower/messaging/execution/select_adapter.rb +63 -0
  278. data/app/services/command_tower/messaging/handoff/advance_communication.rb +39 -0
  279. data/app/services/command_tower/messaging/handoff/operation_logger.rb +78 -0
  280. data/app/services/command_tower/messaging/handoff/recovery.rb +26 -0
  281. data/app/services/command_tower/messaging/inbox/bulk_result.rb +19 -0
  282. data/app/services/command_tower/messaging/inbox/error.rb +9 -0
  283. data/app/services/command_tower/messaging/inbox/invalid_bulk_items_error.rb +19 -0
  284. data/app/services/command_tower/messaging/inbox/invariant_error.rb +9 -0
  285. data/app/services/command_tower/messaging/inbox/item_result.rb +64 -0
  286. data/app/services/command_tower/messaging/inbox/list_result.rb +18 -0
  287. data/app/services/command_tower/messaging/inbox/mutator.rb +252 -0
  288. data/app/services/command_tower/messaging/inbox/not_found_error.rb +9 -0
  289. data/app/services/command_tower/messaging/inbox/operation_logger.rb +37 -0
  290. data/app/services/command_tower/messaging/inbox/reader.rb +117 -0
  291. data/app/services/command_tower/messaging/inbox/scope.rb +24 -0
  292. data/app/services/command_tower/messaging/inbox/unread_count_result.rb +16 -0
  293. data/app/services/command_tower/messaging/inbox/validation_error.rb +9 -0
  294. data/app/services/command_tower/messaging/inbox.rb +61 -0
  295. data/app/services/command_tower/messaging/notification_types/catalog_category.rb +19 -0
  296. data/app/services/command_tower/messaging/notification_types/declaration.rb +96 -0
  297. data/app/services/command_tower/messaging/notification_types/declaration_validator.rb +152 -0
  298. data/app/services/command_tower/messaging/notification_types/duplicate_type_error.rb +9 -0
  299. data/app/services/command_tower/messaging/notification_types/error.rb +9 -0
  300. data/app/services/command_tower/messaging/notification_types/invalid_declaration_error.rb +9 -0
  301. data/app/services/command_tower/messaging/notification_types/not_found_error.rb +9 -0
  302. data/app/services/command_tower/messaging/notification_types/registry.rb +103 -0
  303. data/app/services/command_tower/messaging/notification_types/sealed_registry_error.rb +9 -0
  304. data/app/services/command_tower/messaging/notification_types.rb +37 -0
  305. data/app/services/command_tower/messaging/planner/destination_plan.rb +41 -0
  306. data/app/services/command_tower/messaging/planner/error.rb +9 -0
  307. data/app/services/command_tower/messaging/planner/excluded_destination.rb +28 -0
  308. data/app/services/command_tower/messaging/planner/illegal_override_error.rb +9 -0
  309. data/app/services/command_tower/messaging/planner/impossible_mandatory_plan_error.rb +9 -0
  310. data/app/services/command_tower/messaging/planner/invalid_evaluation_error.rb +9 -0
  311. data/app/services/command_tower/messaging/planner/message_overrides.rb +33 -0
  312. data/app/services/command_tower/messaging/planner/operation_logger.rb +28 -0
  313. data/app/services/command_tower/messaging/planner/planner.rb +261 -0
  314. data/app/services/command_tower/messaging/planner/unknown_type_error.rb +9 -0
  315. data/app/services/command_tower/messaging/planner.rb +25 -0
  316. data/app/services/command_tower/messaging/preferences/catalog.rb +96 -0
  317. data/app/services/command_tower/messaging/preferences/catalog_category_result.rb +19 -0
  318. data/app/services/command_tower/messaging/preferences/catalog_notification_result.rb +55 -0
  319. data/app/services/command_tower/messaging/preferences/catalog_result.rb +13 -0
  320. data/app/services/command_tower/messaging/preferences/error.rb +9 -0
  321. data/app/services/command_tower/messaging/preferences/evaluation_result.rb +61 -0
  322. data/app/services/command_tower/messaging/preferences/evaluator.rb +194 -0
  323. data/app/services/command_tower/messaging/preferences/invalid_preference_state_error.rb +9 -0
  324. data/app/services/command_tower/messaging/preferences/invalid_preference_write_error.rb +10 -0
  325. data/app/services/command_tower/messaging/preferences/not_settings_visible_error.rb +10 -0
  326. data/app/services/command_tower/messaging/preferences/preference_state.rb +61 -0
  327. data/app/services/command_tower/messaging/preferences/reason_classes.rb +17 -0
  328. data/app/services/command_tower/messaging/preferences/resolve.rb +50 -0
  329. data/app/services/command_tower/messaging/preferences/store.rb +126 -0
  330. data/app/services/command_tower/messaging/preferences/store_error.rb +10 -0
  331. data/app/services/command_tower/messaging/preferences/suppressed_destination.rb +24 -0
  332. data/app/services/command_tower/messaging/preferences/unknown_type_error.rb +9 -0
  333. data/app/services/command_tower/messaging/preferences/update.rb +248 -0
  334. data/app/services/command_tower/messaging/preferences.rb +68 -0
  335. data/app/services/command_tower/messaging/pushover/adapters/disabled_adapter.rb +33 -0
  336. data/app/services/command_tower/messaging/pushover/adapters/fake_adapter.rb +90 -0
  337. data/app/services/command_tower/messaging/pushover/adapters/http_adapter.rb +145 -0
  338. data/app/services/command_tower/messaging/pushover/adapters/log_adapter.rb +52 -0
  339. data/app/services/command_tower/messaging/pushover/result.rb +34 -0
  340. data/app/services/command_tower/messaging/pushover/transport.rb +64 -0
  341. data/app/services/command_tower/messaging/recipient_readiness/channel_result.rb +55 -0
  342. data/app/services/command_tower/messaging/recipient_readiness/error.rb +9 -0
  343. data/app/services/command_tower/messaging/recipient_readiness/evaluate.rb +319 -0
  344. data/app/services/command_tower/messaging/recipient_readiness/identity_facts.rb +54 -0
  345. data/app/services/command_tower/messaging/recipient_readiness/reason_codes.rb +33 -0
  346. data/app/services/command_tower/messaging/recipient_readiness/recipient_not_found_error.rb +9 -0
  347. data/app/services/command_tower/messaging/recipient_readiness/recipient_result.rb +38 -0
  348. data/app/services/command_tower/messaging/recipient_readiness/unknown_channel_error.rb +9 -0
  349. data/app/services/command_tower/messaging/recipient_readiness.rb +26 -0
  350. data/app/services/command_tower/messaging/rendering/channel_renderer.rb +127 -0
  351. data/app/services/command_tower/messaging/rendering/render_error.rb +24 -0
  352. data/app/services/command_tower/messaging/rendering/rendered_payload.rb +44 -0
  353. data/app/services/command_tower/messaging/rendering/rendered_pushover_payload.rb +43 -0
  354. data/app/services/command_tower/messaging/rendering/rendered_sms_payload.rb +40 -0
  355. data/app/services/command_tower/messaging.rb +35 -0
  356. data/app/services/command_tower/secrets/cleanse.rb +19 -5
  357. data/app/services/command_tower/secrets/generate.rb +62 -27
  358. data/app/services/command_tower/secrets/verify.rb +46 -12
  359. data/app/services/command_tower/secrets.rb +2 -0
  360. data/app/services/command_tower/services/account/clear_phone.rb +25 -0
  361. data/app/services/command_tower/services/account/phone_verification/send.rb +114 -0
  362. data/app/services/command_tower/services/account/phone_verification/verify.rb +88 -0
  363. data/app/services/command_tower/services/account/pushover/create.rb +28 -0
  364. data/app/services/command_tower/services/account/pushover/ct_support.rb +70 -0
  365. data/app/services/command_tower/services/account/pushover/destroy.rb +30 -0
  366. data/app/services/command_tower/services/account/pushover/replace.rb +28 -0
  367. data/app/services/command_tower/services/account/pushover/show.rb +19 -0
  368. data/app/services/command_tower/services/account/pushover/verify.rb +30 -0
  369. data/app/services/command_tower/services/account/update_phone.rb +50 -0
  370. data/app/services/command_tower/services/application_service.rb +21 -0
  371. data/app/services/command_tower/services/auth/authenticate_session.rb +105 -0
  372. data/app/services/command_tower/services/auth/authorize_request.rb +34 -0
  373. data/app/services/command_tower/services/auth/client_ip_resolver.rb +13 -0
  374. data/app/services/command_tower/services/auth/email_availability.rb +42 -0
  375. data/app/services/command_tower/services/auth/email_verification/send.rb +65 -0
  376. data/app/services/command_tower/services/auth/email_verification/verify.rb +51 -0
  377. data/app/services/command_tower/services/auth/identity_policy.rb +60 -0
  378. data/app/services/command_tower/services/auth/password_recovery/rate_limits/check_send.rb +48 -0
  379. data/app/services/command_tower/services/auth/password_recovery/rate_limits/check_token_issue.rb +35 -0
  380. data/app/services/command_tower/services/auth/password_recovery/rate_limits/enforcement.rb +53 -0
  381. data/app/services/command_tower/services/auth/password_recovery_session/create.rb +26 -0
  382. data/app/services/command_tower/services/auth/password_recovery_session/decode.rb +29 -0
  383. data/app/services/command_tower/services/auth/password_recovery_session/encode.rb +31 -0
  384. data/app/services/command_tower/services/auth/password_recovery_session/validate.rb +58 -0
  385. data/app/services/command_tower/services/auth/password_reset/reset.rb +65 -0
  386. data/app/services/command_tower/services/auth/password_reset/send.rb +77 -0
  387. data/app/services/command_tower/services/auth/password_reset/token_verification.rb +68 -0
  388. data/app/services/command_tower/services/auth/password_reset/validate.rb +29 -0
  389. data/app/services/command_tower/services/auth/plain_text/login.rb +44 -0
  390. data/app/services/command_tower/services/auth/register.rb +77 -0
  391. data/app/services/command_tower/services/auth/signup_rate_limits/check_availability.rb +52 -0
  392. data/app/services/command_tower/services/auth/signup_rate_limits/check_register.rb +24 -0
  393. data/app/services/command_tower/services/auth/signup_rate_limits/check_token_issue.rb +33 -0
  394. data/app/services/command_tower/services/auth/signup_rate_limits/enforcement.rb +50 -0
  395. data/app/services/command_tower/services/auth/signup_session/create.rb +26 -0
  396. data/app/services/command_tower/services/auth/signup_session/decode.rb +29 -0
  397. data/app/services/command_tower/services/auth/signup_session/encode.rb +31 -0
  398. data/app/services/command_tower/services/auth/signup_session/validate.rb +58 -0
  399. data/app/services/command_tower/services/auth/username_availability.rb +28 -0
  400. data/app/services/command_tower/services/me/capabilities.rb +43 -0
  401. data/app/services/command_tower/services/me/change_password.rb +84 -0
  402. data/app/services/command_tower/services/me/pushover_product_gate.rb +18 -0
  403. data/app/services/command_tower/services/me/sms_product_gate.rb +19 -0
  404. data/app/services/command_tower/services/me/update_name.rb +58 -0
  405. data/app/services/command_tower/services/messaging/communications/produce.rb +68 -0
  406. data/app/services/command_tower/services/messaging/communications/produce_many.rb +154 -0
  407. data/app/services/command_tower/services/messaging/inbox.rb +129 -0
  408. data/app/services/command_tower/services/messaging/preferences/platform_enabled_channels.rb +20 -0
  409. data/app/services/command_tower/services/messaging/preferences/show.rb +36 -0
  410. data/app/services/command_tower/services/messaging/preferences/update.rb +43 -0
  411. data/app/services/command_tower/services/messaging/recipients.rb +20 -0
  412. data/app/services/command_tower/services/rate_limits/check.rb +31 -0
  413. data/app/services/command_tower/services/service_result.rb +73 -0
  414. data/app/services/command_tower/user_attributes/mutate.rb +154 -0
  415. data/app/services/command_tower/username/available.rb +24 -11
  416. data/app/views/command_tower/email_verification_mailer/verify_email.html.erb +70 -22
  417. data/app/views/command_tower/messaging/rendering/email.html.erb +28 -0
  418. data/app/views/command_tower/messaging/rendering/email.text.erb +7 -0
  419. data/app/views/command_tower/messaging/rendering/pushover.text.erb +1 -0
  420. data/app/views/command_tower/messaging/rendering/sms.text.erb +1 -0
  421. data/app/views/command_tower/password_reset_mailer/reset_password.html.erb +107 -0
  422. data/app/views/layouts/mailer.html.erb +13 -0
  423. data/app/views/layouts/mailer.text.erb +1 -0
  424. data/app/workflows/command_tower/workflows/admin/messaging/create_announcement_workflow.rb +62 -0
  425. data/app/workflows/command_tower/workflows/application_workflow.rb +68 -0
  426. data/app/workflows/command_tower/workflows/auth/authenticate_request_workflow.rb +40 -0
  427. data/app/workflows/command_tower/workflows/auth/authentication_response_effects.rb +21 -0
  428. data/app/workflows/command_tower/workflows/auth/authorize_request_workflow.rb +28 -0
  429. data/app/workflows/command_tower/workflows/auth/email_availability_workflow.rb +39 -0
  430. data/app/workflows/command_tower/workflows/auth/email_verification/send_workflow.rb +32 -0
  431. data/app/workflows/command_tower/workflows/auth/email_verification/verify_workflow.rb +35 -0
  432. data/app/workflows/command_tower/workflows/auth/identity_error_status.rb +36 -0
  433. data/app/workflows/command_tower/workflows/auth/identity_policy_workflow.rb +26 -0
  434. data/app/workflows/command_tower/workflows/auth/logout_workflow.rb +19 -0
  435. data/app/workflows/command_tower/workflows/auth/password_recovery_session/authenticate_workflow.rb +49 -0
  436. data/app/workflows/command_tower/workflows/auth/password_recovery_session/create_workflow.rb +39 -0
  437. data/app/workflows/command_tower/workflows/auth/password_reset/reset_workflow.rb +40 -0
  438. data/app/workflows/command_tower/workflows/auth/password_reset/send_workflow.rb +45 -0
  439. data/app/workflows/command_tower/workflows/auth/password_reset/validate_workflow.rb +40 -0
  440. data/app/workflows/command_tower/workflows/auth/plain_text/login_workflow.rb +46 -0
  441. data/app/workflows/command_tower/workflows/auth/register_workflow.rb +83 -0
  442. data/app/workflows/command_tower/workflows/auth/session/show_workflow.rb +44 -0
  443. data/app/workflows/command_tower/workflows/auth/session_error_status.rb +30 -0
  444. data/app/workflows/command_tower/workflows/auth/signup_error_status.rb +31 -0
  445. data/app/workflows/command_tower/workflows/auth/signup_session/authenticate_workflow.rb +49 -0
  446. data/app/workflows/command_tower/workflows/auth/signup_session/create_workflow.rb +37 -0
  447. data/app/workflows/command_tower/workflows/auth/username_availability_workflow.rb +39 -0
  448. data/app/workflows/command_tower/workflows/me/change_password_workflow.rb +34 -0
  449. data/app/workflows/command_tower/workflows/me/clear_phone_workflow.rb +52 -0
  450. data/app/workflows/command_tower/workflows/me/error_mapping.rb +46 -0
  451. data/app/workflows/command_tower/workflows/me/error_status.rb +28 -0
  452. data/app/workflows/command_tower/workflows/me/phone_verification/send_workflow.rb +65 -0
  453. data/app/workflows/command_tower/workflows/me/phone_verification/verify_workflow.rb +57 -0
  454. data/app/workflows/command_tower/workflows/me/pushover/create_workflow.rb +38 -0
  455. data/app/workflows/command_tower/workflows/me/pushover/destroy_workflow.rb +34 -0
  456. data/app/workflows/command_tower/workflows/me/pushover/replace_workflow.rb +38 -0
  457. data/app/workflows/command_tower/workflows/me/pushover/show_workflow.rb +34 -0
  458. data/app/workflows/command_tower/workflows/me/pushover/verify_workflow.rb +34 -0
  459. data/app/workflows/command_tower/workflows/me/pushover/workflow_support.rb +31 -0
  460. data/app/workflows/command_tower/workflows/me/show_workflow.rb +33 -0
  461. data/app/workflows/command_tower/workflows/me/update_name_workflow.rb +48 -0
  462. data/app/workflows/command_tower/workflows/me/update_phone_workflow.rb +55 -0
  463. data/app/workflows/command_tower/workflows/messaging/communications/produce_recipient_workflow.rb +60 -0
  464. data/app/workflows/command_tower/workflows/messaging/error_mapping.rb +26 -0
  465. data/app/workflows/command_tower/workflows/messaging/execution/deliver_workflow.rb +234 -0
  466. data/app/workflows/command_tower/workflows/messaging/handoff/process_workflow.rb +90 -0
  467. data/app/workflows/command_tower/workflows/messaging/inbox.rb +97 -0
  468. data/app/workflows/command_tower/workflows/messaging/preferences/show_workflow.rb +31 -0
  469. data/app/workflows/command_tower/workflows/messaging/preferences/update_workflow.rb +37 -0
  470. data/app/workflows/command_tower/workflows/profile/show_workflow.rb +27 -0
  471. data/app/workflows/command_tower/workflows/workflow_result.rb +34 -0
  472. data/config/routes.rb +85 -37
  473. data/db/migrate/{20241117043720_create_command_tower_users.rb → 20260805000001_create_command_tower_users.rb} +7 -9
  474. data/db/migrate/{20241204065708_create_command_tower_user_secrets.rb → 20260805000002_create_command_tower_user_secrets.rb} +3 -0
  475. data/db/migrate/20260805000003_create_command_tower_messaging_core.rb +90 -0
  476. data/db/migrate/20260805000004_create_messaging_notification_preferences.rb +17 -0
  477. data/db/migrate/20260805000005_create_messaging_endpoints_and_pushover_credentials.rb +72 -0
  478. data/docs/api_reference.md +438 -0
  479. data/docs/architecture.md +30 -0
  480. data/docs/authentication.md +37 -0
  481. data/docs/authentication_authorization_guide.md +150 -0
  482. data/docs/authorization.md +34 -0
  483. data/docs/change_password_workflow.md +67 -0
  484. data/docs/controllers.md +35 -0
  485. data/docs/cookie_authentication_guide.md +167 -0
  486. data/docs/email_verification_workflow.md +58 -0
  487. data/docs/extending.md +105 -0
  488. data/docs/host_integration_guide.md +158 -0
  489. data/docs/initializing.md +169 -0
  490. data/docs/messaging_integration_guide.md +87 -0
  491. data/docs/models.md +51 -0
  492. data/docs/pagination.md +47 -0
  493. data/docs/password_reset_workflow.md +82 -0
  494. data/docs/sensitive_routes.md +47 -0
  495. data/docs/testing.md +74 -0
  496. data/docs/upgrades/0.10.0.md +58 -0
  497. data/docs/upgrades/README.md +9 -0
  498. data/lib/command_tower/authorization/default.yml +5 -33
  499. data/lib/command_tower/configuration/application/config.rb +2 -2
  500. data/lib/command_tower/configuration/base.rb +0 -1
  501. data/lib/command_tower/configuration/config.rb +31 -1
  502. data/lib/command_tower/configuration/credentials/config.rb +33 -0
  503. data/lib/command_tower/configuration/credentials/smtp.rb +33 -0
  504. data/lib/command_tower/configuration/credentials/twilio.rb +33 -0
  505. data/lib/command_tower/configuration/email/config.rb +1 -18
  506. data/lib/command_tower/configuration/identity/config.rb +19 -0
  507. data/lib/command_tower/configuration/identity/phone_verification.rb +54 -0
  508. data/lib/command_tower/configuration/jwt/config.rb +7 -0
  509. data/lib/command_tower/configuration/jwt/cookie/config.rb +64 -0
  510. data/lib/command_tower/configuration/jwt/cookie/csrf/config.rb +72 -0
  511. data/lib/command_tower/configuration/login/strategy/plain_text/config.rb +6 -0
  512. data/lib/command_tower/configuration/login/strategy/plain_text/email_verify.rb +5 -0
  513. data/lib/command_tower/configuration/login/strategy/plain_text/password_reset.rb +49 -0
  514. data/lib/command_tower/configuration/messaging/config.rb +60 -0
  515. data/lib/command_tower/configuration/messaging/pushover.rb +42 -0
  516. data/lib/command_tower/configuration/messaging/sms.rb +32 -0
  517. data/lib/command_tower/configuration/password_recovery_session/config.rb +61 -0
  518. data/lib/command_tower/configuration/password_recovery_session/rate_limits.rb +33 -0
  519. data/lib/command_tower/configuration/signup_session/config.rb +67 -0
  520. data/lib/command_tower/configuration/signup_session/email_availability.rb +18 -0
  521. data/lib/command_tower/configuration/signup_session/rate_limits.rb +48 -0
  522. data/lib/command_tower/configuration/user/config.rb +6 -5
  523. data/lib/command_tower/credential_resolution/env_backend.rb +25 -0
  524. data/lib/command_tower/credential_resolution/smtp_action_mailer_bridge.rb +24 -0
  525. data/lib/command_tower/credential_resolution/smtp_credentials.rb +26 -0
  526. data/lib/command_tower/credential_resolution/twilio_credentials.rb +26 -0
  527. data/lib/command_tower/credential_resolution.rb +78 -0
  528. data/lib/command_tower/current.rb +7 -0
  529. data/lib/command_tower/engine.rb +4 -1
  530. data/lib/command_tower/install/baseline.rb +31 -0
  531. data/lib/command_tower/install/doctor.rb +227 -0
  532. data/lib/command_tower/jwt/authorization_helper.rb +136 -0
  533. data/lib/command_tower/jwt/csrf_helper.rb +138 -0
  534. data/lib/command_tower/password_recovery/authorization_helper.rb +25 -0
  535. data/lib/command_tower/redis_connection.rb +21 -0
  536. data/lib/command_tower/schema/error/email_validation_required.rb +15 -0
  537. data/lib/command_tower/schema/shared/user.rb +32 -0
  538. data/lib/command_tower/schema.rb +2 -25
  539. data/lib/command_tower/signup/authorization_helper.rb +25 -0
  540. data/lib/command_tower/spec_helper.rb +58 -1
  541. data/lib/command_tower/testing.rb +62 -0
  542. data/lib/command_tower/version.rb +1 -1
  543. data/lib/command_tower.rb +20 -0
  544. data/lib/generators/command_tower/configure/USAGE +14 -2
  545. data/lib/generators/command_tower/configure/configure_generator.rb +29 -2
  546. data/lib/tasks/auto_annotate_models.rake +23 -55
  547. data/lib/tasks/install.rake +81 -0
  548. data/lib/tasks/users.rake +119 -0
  549. data/spec/factories/entity.rb +37 -0
  550. data/spec/factories/messaging.rb +156 -0
  551. data/spec/factories/role.rb +29 -0
  552. data/spec/factories/user.rb +53 -0
  553. data/spec/factories/user_secret.rb +12 -0
  554. metadata +576 -55
  555. data/app/controllers/command_tower/admin_controller.rb +0 -110
  556. data/app/controllers/command_tower/auth/plain_text_controller.rb +0 -133
  557. data/app/controllers/command_tower/inbox/message_blast_controller.rb +0 -89
  558. data/app/controllers/command_tower/inbox/message_controller.rb +0 -81
  559. data/app/controllers/command_tower/user_controller.rb +0 -49
  560. data/app/controllers/command_tower/username_controller.rb +0 -26
  561. data/app/helpers/command_tower/pagination_helper.rb +0 -40
  562. data/app/helpers/command_tower/schema_helper.rb +0 -29
  563. data/app/models/message.rb +0 -30
  564. data/app/models/message_blast.rb +0 -27
  565. data/app/services/command_tower/admin_service/users.rb +0 -27
  566. data/app/services/command_tower/inbox_service/blast/delete.rb +0 -23
  567. data/app/services/command_tower/inbox_service/blast/metadata.rb +0 -26
  568. data/app/services/command_tower/inbox_service/blast/new_user_blaster.rb +0 -24
  569. data/app/services/command_tower/inbox_service/blast/retrieve.rb +0 -30
  570. data/app/services/command_tower/inbox_service/blast/upsert.rb +0 -67
  571. data/app/services/command_tower/inbox_service/message/metadata.rb +0 -41
  572. data/app/services/command_tower/inbox_service/message/modify.rb +0 -44
  573. data/app/services/command_tower/inbox_service/message/retrieve.rb +0 -36
  574. data/app/services/command_tower/inbox_service/message/send.rb +0 -33
  575. data/app/services/command_tower/jwt/time_delay_token.rb +0 -17
  576. data/app/services/command_tower/login_strategy/plain_text/create.rb +0 -43
  577. data/app/services/command_tower/login_strategy/plain_text/email_verification/generate.rb +0 -29
  578. data/app/services/command_tower/login_strategy/plain_text/email_verification/send.rb +0 -23
  579. data/app/services/command_tower/login_strategy/plain_text/email_verification/verify.rb +0 -24
  580. data/app/services/command_tower/login_strategy/plain_text/login.rb +0 -50
  581. data/app/services/command_tower/pagination_service_helper.rb +0 -88
  582. data/app/services/command_tower/user_attributes/modify.rb +0 -68
  583. data/app/services/command_tower/user_attributes/roles.rb +0 -27
  584. data/db/migrate/20250223023306_create_command_tower_messages.rb +0 -12
  585. data/db/migrate/20250223023313_create_command_tower_message_blasts.rb +0 -14
  586. data/lib/command_tower/schema/admin/users.rb +0 -16
  587. data/lib/command_tower/schema/inbox/blast_request.rb +0 -15
  588. data/lib/command_tower/schema/inbox/blast_response.rb +0 -16
  589. data/lib/command_tower/schema/inbox/message_blast_entity.rb +0 -16
  590. data/lib/command_tower/schema/inbox/message_blast_metadata.rb +0 -16
  591. data/lib/command_tower/schema/inbox/message_entity.rb +0 -14
  592. data/lib/command_tower/schema/inbox/metadata.rb +0 -18
  593. data/lib/command_tower/schema/inbox/modified.rb +0 -13
  594. data/lib/command_tower/schema/page.rb +0 -11
  595. data/lib/command_tower/schema/pagination.rb +0 -16
  596. data/lib/command_tower/schema/plain_text/create_user_request.rb +0 -18
  597. data/lib/command_tower/schema/plain_text/create_user_response.rb +0 -17
  598. data/lib/command_tower/schema/plain_text/email_verify_request.rb +0 -11
  599. data/lib/command_tower/schema/plain_text/email_verify_response.rb +0 -11
  600. data/lib/command_tower/schema/plain_text/email_verify_send_request.rb +0 -9
  601. data/lib/command_tower/schema/plain_text/email_verify_send_response.rb +0 -11
  602. data/lib/command_tower/schema/plain_text/login_request.rb +0 -15
  603. data/lib/command_tower/schema/plain_text/login_response.rb +0 -16
  604. data/lib/command_tower/schema/user.rb +0 -28
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Serializers
5
+ module Me
6
+ class PushoverSerializer
7
+ def self.serialize(safe_view)
8
+ new(safe_view).serialize
9
+ end
10
+
11
+ def self.unconfigured
12
+ {
13
+ configured: false,
14
+ actions: {
15
+ canCreate: true,
16
+ canVerify: false,
17
+ canReplace: false,
18
+ canRemove: false
19
+ }
20
+ }
21
+ end
22
+
23
+ def initialize(safe_view)
24
+ @safe_view = safe_view
25
+ end
26
+
27
+ def serialize
28
+ return self.class.unconfigured if @safe_view.nil?
29
+
30
+ {
31
+ configured: true,
32
+ id: @safe_view.id,
33
+ channelKey: @safe_view.channel_key,
34
+ lifecycleState: @safe_view.lifecycle_state,
35
+ verificationState: @safe_view.verification_state,
36
+ maskedDisplayValue: @safe_view.masked_display_value,
37
+ credentialsConfigured: !!@safe_view.credentials_configured,
38
+ verifiedAt: @safe_view.verified_at&.iso8601,
39
+ createdAt: @safe_view.created_at&.iso8601,
40
+ updatedAt: @safe_view.updated_at&.iso8601,
41
+ actions: actions_for(@safe_view)
42
+ }
43
+ end
44
+
45
+ private
46
+
47
+ def actions_for(view)
48
+ active = view.lifecycle_state.to_s == "active"
49
+ {
50
+ canCreate: false,
51
+ canVerify: active,
52
+ canReplace: active,
53
+ canRemove: active
54
+ }
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Serializers
5
+ module Messaging
6
+ module Inbox
7
+ class ItemSerializer
8
+ def self.serialize(item)
9
+ { id: item.fetch(:id), title: item.fetch(:title), status: item.fetch(:status),
10
+ read: item[:viewed_at].present?, viewedAt: item[:viewed_at]&.iso8601,
11
+ createdAt: item[:created_at]&.iso8601, updatedAt: item[:updated_at]&.iso8601 }
12
+ end
13
+ end
14
+
15
+ class DetailSerializer
16
+ def self.serialize(item)
17
+ ItemSerializer.serialize(item).merge(
18
+ body: item.fetch(:body), metadata: item[:metadata],
19
+ notificationTypeKey: item.fetch(:notification_type_key)
20
+ )
21
+ end
22
+ end
23
+
24
+ class BulkResultSerializer
25
+ def self.serialize(result)
26
+ { ids: result.fetch(:ids), count: result.fetch(:count), changedCount: result.fetch(:changed_count) }
27
+ end
28
+ end
29
+
30
+ class UnreadCountSerializer
31
+ def self.serialize(payload)
32
+ { count: payload.fetch(:count) }
33
+ end
34
+ end
35
+
36
+ class PaginationMetaSerializer
37
+ def self.serialize(pagination)
38
+ { limit: pagination.fetch(:limit), offset: pagination.fetch(:offset),
39
+ totalCount: pagination.fetch(:total_count) }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Serializers
5
+ module Messaging
6
+ module Preferences
7
+ class CatalogSerializer
8
+ def self.serialize(catalog)
9
+ {
10
+ categories: catalog.categories.map { |category| CategorySerializer.serialize(category) },
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Serializers
5
+ module Messaging
6
+ module Preferences
7
+ class CategorySerializer
8
+ def self.serialize(category)
9
+ {
10
+ key: category.key,
11
+ label: category.label,
12
+ description: category.description,
13
+ order: category.order,
14
+ notifications: category.notifications.map { |notification|
15
+ NotificationSerializer.serialize(notification)
16
+ },
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Serializers
5
+ module Messaging
6
+ module Preferences
7
+ class NotificationSerializer
8
+ def self.serialize(notification)
9
+ {
10
+ key: notification.key,
11
+ label: notification.label,
12
+ description: notification.description,
13
+ order: notification.order,
14
+ configurable: notification.configurable,
15
+ mandatory: notification.mandatory,
16
+ inboxAvailable: notification.inbox_available,
17
+ allowedChannels: notification.allowed_channels,
18
+ availableChannels: notification.available_channels,
19
+ preferences: {
20
+ inboxEnabled: notification.inbox_enabled,
21
+ channels: notification.channels,
22
+ storedOverridePresent: notification.stored_override_present,
23
+ },
24
+ }
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Serializers
5
+ module Profile
6
+ class ProfileSerializer
7
+ def self.serialize(user)
8
+ CommandTower::Serializers::Auth::UserSerializer.serialize(user)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,12 +1,24 @@
1
- # CommandTower Service
1
+ # CommandTower ServiceBase
2
2
 
3
- `CommandTower::ServiceBase` an abstraction around the Ruby Gem Interactor. It dds custom functionality to the base Service and is intended to be an inherited Class to create Application logic code. All Services in `CommandTower` utilize this base Service class for convenience and DRYness.
3
+ `CommandTower::ServiceBase` is an abstraction around the Ruby gem Interactor. It adds logging and argument validation on top of Interactor and is the inherited base for **service** classes that implement one business capability.
4
+
5
+ Services sit **under** workflows in the platform layer map:
6
+
7
+ ```text
8
+ Controller / Job → Workflow → Shared Sequences / Services → Models & Clients
9
+ ```
10
+
11
+ Workflows own orchestration of a business action. Services own how a single capability is performed. Do not treat ServiceBase as the application orchestrator — that is the workflow layer.
12
+
13
+ Long-form architecture rationale: `artifacts/rails_architecture_handbook.md` (workspace). Condensed enforcement: `.cursor/rules/rails-architecture.mdc` and `.cursor/rules/services.mdc`.
14
+
15
+ All services in CommandTower that use this pattern inherit `CommandTower::ServiceBase` for consistency.
4
16
 
5
17
  ## What does ServiceBase offer
6
18
 
7
19
  ### Logging
8
20
  `ServiceBase` offers a convenient way to tag logs. It keeps track of:
9
- - The start of the the Logic call
21
+ - The start of the logic call
10
22
  - The time it took to complete the logic
11
23
  - The status of the logic
12
24
 
@@ -39,11 +51,15 @@ Other more complex Argument validation includes:
39
51
  - Delegate context variable to the class for simplicity
40
52
  - Validating Argument length or size is `<` `≤` `==` `>` `≥`
41
53
 
42
- For More information, Check out the [ArgumentValidation ReadMe](argument_validation/README.md)
54
+ For more information, see the [ArgumentValidation ReadMe](argument_validation/README.md)
43
55
 
44
56
 
45
- ## Basic Examples:
46
- Check out the examples used in this directory!
57
+ ## Examples in the gem
47
58
 
59
+ There is no separate examples directory under this path. Real service classes live under capability namespaces, for example:
48
60
 
61
+ - `CommandTower::Services::Messaging::Communications::Produce`
62
+ - `CommandTower::Services::Messaging::Communications::ProduceMany`
63
+ - `CommandTower::Services::Me::ChangePassword`
49
64
 
65
+ See [Messaging](../../../docs/messaging_integration_guide.md) and [Extending](../../../docs/extending.md).
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Auth
5
+ # Transport handle passed from a controller boundary into authentication.
6
+ # Keeps the request/response pair together so services never reach for
7
+ # controller state directly. Not a workflow type.
8
+ class RequestContext
9
+ attr_reader :request, :response
10
+
11
+ def self.from(request, response)
12
+ new(request:, response:)
13
+ end
14
+
15
+ def initialize(request:, response:)
16
+ @request = request
17
+ @response = response
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,43 +1,79 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommandTower::Authorize
4
- class Validate < CommandTower::ServiceBase
5
- on_argument_validation :fail_early
4
+ # RBAC decision for a controller action. Shared by the engine's
5
+ # `authorize_user!` transport filter and the modern AuthorizeRequest service,
6
+ # so it stays a plain domain object with a narrow decision contract.
7
+ class Validate
8
+ include CommandTower::ServiceLogging
6
9
 
7
- validate :user, is_a: User, required: true
8
- validate :controller, is_a: [ActionController::Base, ActionController::API], required: true
9
- validate :method, is_a: String, required: true
10
+ NOT_REQUIRED_MSG = "Authorization not required at this time"
11
+ AUTHORIZED_MSG = "User is Authorized for action"
12
+ UNAUTHORIZED_MSG = "Unauthorized Access. Incorrect User Privileges"
13
+
14
+ class Decision
15
+ attr_reader :user, :authorization_required, :msg
16
+
17
+ def self.authorized(user:, authorization_required:, msg:)
18
+ new(success: true, user:, authorization_required:, msg:)
19
+ end
20
+
21
+ def self.denied(user:, msg:)
22
+ new(success: false, user:, authorization_required: true, msg:)
23
+ end
24
+
25
+ def initialize(success:, user:, authorization_required:, msg:)
26
+ @success = success
27
+ @user = user
28
+ @authorization_required = authorization_required
29
+ @msg = msg
30
+ end
31
+
32
+ def success? = @success
33
+
34
+ def failure? = !@success
35
+ end
36
+
37
+ def self.call(user:, controller:, method:)
38
+ new(user:, controller:, method:).call
39
+ end
40
+
41
+ def initialize(user:, controller:, method:)
42
+ @user = user
43
+ @controller = controller
44
+ @action = method
45
+ end
10
46
 
11
47
  def call
12
- context.authorization_required = authorization_required?
13
- unless context.authorization_required
14
- log_info("controller:#{controller}; method:#{method} -- No Authorization required")
15
- context.msg = "Authorization not required at this time"
16
- return
48
+ unless authorization_required?
49
+ log_info("controller:#{controller}; method:#{action} -- No Authorization required")
50
+ return Decision.authorized(user:, authorization_required: false, msg: NOT_REQUIRED_MSG)
17
51
  end
18
52
 
19
53
  # At this point we know authorization on the route is required
20
54
  # Iterate through the users roles to find a matching role that allows authorization
21
55
  # If at least 1 of the users roles passes validation, we can allow access to the path
22
56
  log_info("User Roles: #{user.roles}")
23
- auhorization_result = user_role_objects.any? do |_role_name, role_object|
24
- result = role_object.authorized?(controller:, method:, user:)
57
+ authorized = user_role_objects.any? do |_role_name, role_object|
58
+ result = role_object.authorized?(controller:, method: action, user:)
25
59
  log_info("Role:#{result[:role]};Authorized:[#{result[:authorized]}];Reason:[#{result[:reason]}]")
26
60
  result[:authorized] == true
27
61
  end
28
62
 
29
- if auhorization_result
30
- context.msg = "User is Authorized for action"
31
- else
32
- context.fail!(msg: "Unauthorized Access. Incorrect User Privileges")
33
- end
63
+ return Decision.denied(user:, msg: UNAUTHORIZED_MSG) unless authorized
64
+
65
+ Decision.authorized(user:, authorization_required: true, msg: AUTHORIZED_MSG)
34
66
  end
35
67
 
68
+ private
69
+
70
+ attr_reader :user, :controller, :action
71
+
36
72
  def authorization_required?
37
73
  controller_mapping = CommandTower::Authorization.mapped_controllers[controller]
38
74
  return false if controller_mapping.nil?
39
75
 
40
- controller_mapping.include?(method.to_sym)
76
+ controller_mapping.include?(action.to_sym)
41
77
  end
42
78
 
43
79
  def user_role_objects
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Clients
5
+ class ClientBase
6
+ class << self
7
+ # Internal: resolve a provider class under +root+ via Zeitwerk const_get.
8
+ # Host product entry extends Invocation so +root+ is the host Clients module.
9
+ # Not the locked product invocation API.
10
+ def resolve_provider!(name, root: Clients)
11
+ const_name = ConstResolution.normalize_name(name)
12
+ path = "#{root.name}::#{const_name}"
13
+ klass = ConstResolution.const_get!(root, const_name, expected: "provider")
14
+
15
+ unless klass.is_a?(Class) && klass < ClientBase
16
+ raise Errors::DiscoveryError,
17
+ "invalid provider inheritance: expected #{path} < #{ClientBase.name}"
18
+ end
19
+
20
+ klass
21
+ end
22
+ end
23
+
24
+ def initialize(transport: Transport::FaradayAdapter.new(config: Transport::FaradayConfig.from_env))
25
+ raise Errors::ConfigurationError, "transport is required" if transport.nil?
26
+
27
+ @transport = transport
28
+ @namespace_mutex = Mutex.new
29
+ @namespaces = {}
30
+ end
31
+
32
+ # Internal: resolve a namespace module under this provider and wrap NamespaceProxy.
33
+ # Not the locked product invocation API.
34
+ def resolve_namespace!(name)
35
+ const_name = ConstResolution.normalize_name(name)
36
+
37
+ @namespace_mutex.synchronize do
38
+ @namespaces[const_name] ||= build_namespace_proxy!(const_name)
39
+ end
40
+ end
41
+
42
+ # Primary execution contract.
43
+ # Accepts a normalized Clients::Transport::Request.
44
+ # Optional +context:+ is internal framework plumbing (typically ScopedClient).
45
+ def execute(request, context: nil)
46
+ validate_request!(request)
47
+
48
+ prepared = prepare_request(request, context: context)
49
+ started_at = monotonic_now
50
+
51
+ response = call_transport(prepared, started_at)
52
+ return response if response.is_a?(ClientResult)
53
+
54
+ build_result(response, started_at)
55
+ end
56
+
57
+ # Provider-owned authentication check. Default is a no-op so bare/test
58
+ # providers keep working; ScopedClient delegates opaque scope kwargs.
59
+ def enforce_authentication!(**)
60
+ nil
61
+ end
62
+
63
+ # Strip the transport wrapper before resource deserializers run.
64
+ # Default payload is the raw body — providers own encoding (JSON, XML, …).
65
+ def decode_response(response)
66
+ DecodedResponse.new(
67
+ payload: response.body,
68
+ provider_metadata: {}
69
+ )
70
+ end
71
+
72
+ protected
73
+
74
+ # Provider host root (and optional path prefix). Required only when the
75
+ # request URL is relative. Absolute request URLs are preserved as-is.
76
+ def base_url
77
+ nil
78
+ end
79
+
80
+ def default_headers
81
+ {}
82
+ end
83
+
84
+ def apply_authentication(request, context: nil)
85
+ request
86
+ end
87
+
88
+ def map_provider_error(response)
89
+ Errors::UpstreamError.new(
90
+ message: "Upstream request failed with status #{response.status}",
91
+ details: { status: response.status }
92
+ )
93
+ end
94
+
95
+ private
96
+
97
+ def build_namespace_proxy!(const_name)
98
+ path = "#{self.class.name}::#{const_name}"
99
+ mod = ConstResolution.const_get!(self.class, const_name, expected: "namespace")
100
+
101
+ unless mod.instance_of?(Module)
102
+ raise Errors::DiscoveryError,
103
+ "invalid namespace: expected #{path} to be a Module (Zeitwerk folder)"
104
+ end
105
+
106
+ NamespaceProxy.new(client: self, namespace_module: mod)
107
+ end
108
+
109
+ def validate_request!(request)
110
+ return if request.is_a?(Transport::Request)
111
+
112
+ raise Errors::ConfigurationError,
113
+ "request must be a #{Transport::Request.name}"
114
+ end
115
+
116
+ def prepare_request(request, context: nil)
117
+ with_defaults = Transport::Request.build(
118
+ method: request.method,
119
+ url: absolutize_url(request.url),
120
+ headers: default_headers.merge(request.headers),
121
+ body: request.body,
122
+ query: request.query,
123
+ timeout: request.timeout
124
+ )
125
+ apply_authentication(with_defaults, context: context)
126
+ end
127
+
128
+ def absolutize_url(url)
129
+ return url.to_s if Url.absolute?(url)
130
+
131
+ Url.join(base_url, url)
132
+ end
133
+
134
+ def call_transport(prepared, started_at)
135
+ response = @transport.call(prepared)
136
+ unless response.is_a?(Transport::Response)
137
+ raise Errors::ConfigurationError,
138
+ "transport must return a #{Transport::Response.name}"
139
+ end
140
+ response
141
+ rescue Transport::Error => e
142
+ ClientResult.failure(
143
+ error: Errors::UpstreamError.new(message: e.message, cause: e),
144
+ metadata: natural_metadata(status: nil, started_at: started_at, response: nil)
145
+ )
146
+ end
147
+
148
+ def build_result(response, started_at)
149
+ metadata = natural_metadata(
150
+ status: response.status,
151
+ started_at: started_at,
152
+ response: response
153
+ )
154
+
155
+ if response.success?
156
+ ClientResult.success(output: response, metadata: metadata)
157
+ else
158
+ ClientResult.failure(
159
+ error: map_provider_error(response),
160
+ output: response,
161
+ metadata: metadata
162
+ )
163
+ end
164
+ end
165
+
166
+ def natural_metadata(status:, started_at:, response:)
167
+ metadata = {}
168
+ metadata[:status] = status unless status.nil?
169
+ duration_ms = response&.duration_ms || elapsed_ms(started_at)
170
+ metadata[:duration_ms] = duration_ms unless duration_ms.nil?
171
+ metadata
172
+ end
173
+
174
+ def monotonic_now
175
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
176
+ end
177
+
178
+ def elapsed_ms(started_at)
179
+ ((monotonic_now - started_at) * 1000).round
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Clients
5
+ class ClientResult
6
+ attr_reader :output, :error, :metadata, :provider_metadata
7
+
8
+ def initialize(success:, output:, error:, metadata:, provider_metadata: {})
9
+ @success = success
10
+ @output = output
11
+ @error = error
12
+ @metadata = metadata
13
+ @provider_metadata = provider_metadata
14
+ end
15
+
16
+ def success?
17
+ @success
18
+ end
19
+
20
+ def failure?
21
+ !success?
22
+ end
23
+
24
+ def self.success(output:, metadata: {}, provider_metadata: {})
25
+ new(
26
+ success: true,
27
+ output: output,
28
+ error: nil,
29
+ metadata: metadata,
30
+ provider_metadata: provider_metadata
31
+ )
32
+ end
33
+
34
+ def self.failure(error:, output: nil, metadata: {}, provider_metadata: {})
35
+ new(
36
+ success: false,
37
+ output: output,
38
+ error: error,
39
+ metadata: metadata,
40
+ provider_metadata: provider_metadata
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Clients
5
+ # Internal constant helpers for provider/namespace/endpoint resolution.
6
+ # Not a public product API — used by ClientBase / NamespaceProxy and tests.
7
+ module ConstResolution
8
+ module_function
9
+
10
+ def normalize_name(name)
11
+ raw = name.to_s.strip
12
+ if raw.empty?
13
+ raise Errors::DiscoveryError, "discovery name must be present"
14
+ end
15
+
16
+ raw.camelize
17
+ end
18
+
19
+ def const_get!(parent, name, expected:)
20
+ const_name = normalize_name(name)
21
+ path = "#{parent.name}::#{const_name}"
22
+
23
+ begin
24
+ parent.const_get(const_name, false)
25
+ rescue NameError
26
+ raise Errors::DiscoveryError,
27
+ "missing #{expected}: expected #{path}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Clients
5
+ # Internal handoff from provider decode_response → EndpointBase resource deserializer.
6
+ # Not a public product wrapper; not placed on ClientResult#output.
7
+ class DecodedResponse
8
+ attr_reader :payload, :provider_metadata
9
+
10
+ def initialize(payload:, provider_metadata: {})
11
+ @payload = payload
12
+ @provider_metadata = provider_metadata
13
+ end
14
+ end
15
+ end
16
+ end