command_tower 0.3.1 → 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 (603) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +104 -28
  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 +41 -5
  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/pagination/config.rb +16 -0
  518. data/lib/command_tower/configuration/password_recovery_session/config.rb +61 -0
  519. data/lib/command_tower/configuration/password_recovery_session/rate_limits.rb +33 -0
  520. data/lib/command_tower/configuration/signup_session/config.rb +67 -0
  521. data/lib/command_tower/configuration/signup_session/email_availability.rb +18 -0
  522. data/lib/command_tower/configuration/signup_session/rate_limits.rb +48 -0
  523. data/lib/command_tower/configuration/user/config.rb +6 -5
  524. data/lib/command_tower/credential_resolution/env_backend.rb +25 -0
  525. data/lib/command_tower/credential_resolution/smtp_action_mailer_bridge.rb +24 -0
  526. data/lib/command_tower/credential_resolution/smtp_credentials.rb +26 -0
  527. data/lib/command_tower/credential_resolution/twilio_credentials.rb +26 -0
  528. data/lib/command_tower/credential_resolution.rb +78 -0
  529. data/lib/command_tower/current.rb +7 -0
  530. data/lib/command_tower/engine.rb +4 -1
  531. data/lib/command_tower/install/baseline.rb +31 -0
  532. data/lib/command_tower/install/doctor.rb +227 -0
  533. data/lib/command_tower/jwt/authorization_helper.rb +136 -0
  534. data/lib/command_tower/jwt/csrf_helper.rb +138 -0
  535. data/lib/command_tower/password_recovery/authorization_helper.rb +25 -0
  536. data/lib/command_tower/redis_connection.rb +21 -0
  537. data/lib/command_tower/schema/error/email_validation_required.rb +15 -0
  538. data/lib/command_tower/schema/shared/user.rb +32 -0
  539. data/lib/command_tower/schema.rb +2 -25
  540. data/lib/command_tower/signup/authorization_helper.rb +25 -0
  541. data/lib/command_tower/spec_helper.rb +58 -1
  542. data/lib/command_tower/testing.rb +62 -0
  543. data/lib/command_tower/version.rb +1 -1
  544. data/lib/command_tower.rb +20 -0
  545. data/lib/generators/command_tower/configure/USAGE +20 -0
  546. data/lib/generators/command_tower/configure/configure_generator.rb +39 -0
  547. data/lib/tasks/auto_annotate_models.rake +23 -55
  548. data/lib/tasks/install.rake +81 -0
  549. data/lib/tasks/users.rake +119 -0
  550. data/spec/factories/entity.rb +37 -0
  551. data/spec/factories/messaging.rb +156 -0
  552. data/spec/factories/role.rb +29 -0
  553. data/spec/factories/user.rb +53 -0
  554. data/spec/factories/user_secret.rb +12 -0
  555. metadata +579 -53
  556. data/app/controllers/command_tower/admin_controller.rb +0 -104
  557. data/app/controllers/command_tower/auth/plain_text_controller.rb +0 -132
  558. data/app/controllers/command_tower/inbox/message_blast_controller.rb +0 -89
  559. data/app/controllers/command_tower/inbox/message_controller.rb +0 -79
  560. data/app/controllers/command_tower/user_controller.rb +0 -49
  561. data/app/controllers/command_tower/username_controller.rb +0 -26
  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/inbox_service/blast/delete.rb +0 -23
  566. data/app/services/command_tower/inbox_service/blast/metadata.rb +0 -26
  567. data/app/services/command_tower/inbox_service/blast/new_user_blaster.rb +0 -24
  568. data/app/services/command_tower/inbox_service/blast/retrieve.rb +0 -30
  569. data/app/services/command_tower/inbox_service/blast/upsert.rb +0 -67
  570. data/app/services/command_tower/inbox_service/message/metadata.rb +0 -35
  571. data/app/services/command_tower/inbox_service/message/modify.rb +0 -44
  572. data/app/services/command_tower/inbox_service/message/retrieve.rb +0 -36
  573. data/app/services/command_tower/inbox_service/message/send.rb +0 -33
  574. data/app/services/command_tower/jwt/time_delay_token.rb +0 -17
  575. data/app/services/command_tower/login_strategy/plain_text/create.rb +0 -43
  576. data/app/services/command_tower/login_strategy/plain_text/email_verification/generate.rb +0 -29
  577. data/app/services/command_tower/login_strategy/plain_text/email_verification/send.rb +0 -23
  578. data/app/services/command_tower/login_strategy/plain_text/email_verification/verify.rb +0 -24
  579. data/app/services/command_tower/login_strategy/plain_text/login.rb +0 -50
  580. data/app/services/command_tower/user_attributes/modify.rb +0 -68
  581. data/app/services/command_tower/user_attributes/roles.rb +0 -27
  582. data/db/migrate/20250223023306_create_command_tower_messages.rb +0 -12
  583. data/db/migrate/20250223023313_create_command_tower_message_blasts.rb +0 -14
  584. data/lib/command_tower/schema/admin/users.rb +0 -15
  585. data/lib/command_tower/schema/inbox/blast_request.rb +0 -15
  586. data/lib/command_tower/schema/inbox/blast_response.rb +0 -16
  587. data/lib/command_tower/schema/inbox/message_blast_entity.rb +0 -16
  588. data/lib/command_tower/schema/inbox/message_blast_metadata.rb +0 -16
  589. data/lib/command_tower/schema/inbox/message_entity.rb +0 -14
  590. data/lib/command_tower/schema/inbox/metadata.rb +0 -18
  591. data/lib/command_tower/schema/inbox/modified.rb +0 -13
  592. data/lib/command_tower/schema/page.rb +0 -14
  593. data/lib/command_tower/schema/plain_text/create_user_request.rb +0 -18
  594. data/lib/command_tower/schema/plain_text/create_user_response.rb +0 -17
  595. data/lib/command_tower/schema/plain_text/email_verify_request.rb +0 -11
  596. data/lib/command_tower/schema/plain_text/email_verify_response.rb +0 -11
  597. data/lib/command_tower/schema/plain_text/email_verify_send_request.rb +0 -9
  598. data/lib/command_tower/schema/plain_text/email_verify_send_response.rb +0 -11
  599. data/lib/command_tower/schema/plain_text/login_request.rb +0 -15
  600. data/lib/command_tower/schema/plain_text/login_response.rb +0 -13
  601. data/lib/command_tower/schema/user.rb +0 -28
  602. data/lib/generators/api_engine_base/configure/USAGE +0 -8
  603. data/lib/generators/api_engine_base/configure/configure_generator.rb +0 -12
@@ -1,86 +1,113 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommandTower::Jwt
4
- class AuthenticateUser < CommandTower::ServiceBase
4
+ class AuthenticateUser
5
+ INVALID_TOKEN_MSG = "Unauthorized Access. Invalid Authorization token"
6
+ STALE_TOKEN_MSG = "Unauthorized Access. Token is no longer valid"
7
+ EMAIL_VERIFICATION_MSG = "Email must be verified to continue"
8
+ EMAIL_VERIFICATION_STATUS = 412
9
+
10
+ def self.call(token:, bypass_email_validation: false, with_reset: false)
11
+ new(token:, bypass_email_validation:, with_reset:).call
12
+ end
5
13
 
6
- validate :token, is_a: String, required: true, sensitive: true
7
- validate :bypass_email_validation, is_one: [true, false], default: false
8
- validate :with_reset, is_one: [true, false], default: false
14
+ def initialize(token:, bypass_email_validation: false, with_reset: false)
15
+ @token = token
16
+ @bypass_email_validation = !!bypass_email_validation
17
+ @with_reset = !!with_reset
18
+ end
9
19
 
10
20
  def call
11
- result = Decode.(token:)
21
+ decoded = Decode.(token:)
22
+ return invalid_token if decoded.failure?
12
23
 
13
- if result.failure?
14
- context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
15
- end
16
- payload = result.payload
24
+ payload = decoded.payload
17
25
 
18
- expires_at = validate_generated_at!(generated_at: payload[:generated_at])
26
+ expires_at = expires_at_for(generated_at: payload[:generated_at])
27
+ return invalid_token if expires_at.nil?
19
28
 
20
- user = User.find(payload[:user_id]) rescue nil
29
+ user = User.find_by(id: payload[:user_id])
21
30
  if user.nil?
22
31
  log_warn("user_id [#{payload[:user_id]}] was not found. Cannot Continue")
23
- context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
32
+ return invalid_token
24
33
  end
25
34
 
26
- if user.verifier_token == payload[:verifier_token]
27
- context.user = user
28
- else
29
- context.fail!(msg: "Unauthorized Access. Token is no longer valid")
35
+ unless user.verifier_token == payload[:verifier_token]
36
+ return AuthenticationOutcome.failure(msg: STALE_TOKEN_MSG)
30
37
  end
31
38
 
32
- email_validation_required!(user:)
39
+ if email_validation_required?(user:)
40
+ return AuthenticationOutcome.failure(
41
+ msg: EMAIL_VERIFICATION_MSG,
42
+ status: EMAIL_VERIFICATION_STATUS,
43
+ user:
44
+ )
45
+ end
33
46
 
47
+ generated_token = nil
34
48
  if with_reset
35
- context.generated_token = CommandTower::Jwt::LoginCreate.(user:).token
49
+ generated_token = LoginCreate.(user:).token
36
50
  expires_at = CommandTower.config.jwt.ttl.from_now.to_time
37
51
  end
38
52
 
39
- context.expires_at = expires_at.to_s
53
+ AuthenticationOutcome.success(user:, expires_at: expires_at.to_s, generated_token:)
40
54
  end
41
55
 
42
- def validate_generated_at!(generated_at:)
56
+ private
57
+
58
+ attr_reader :token, :bypass_email_validation, :with_reset
59
+
60
+ def invalid_token
61
+ AuthenticationOutcome.failure(msg: INVALID_TOKEN_MSG)
62
+ end
63
+
64
+ # Returns nil when the token cannot be trusted to carry a usable expiration.
65
+ def expires_at_for(generated_at:)
43
66
  if generated_at.nil?
44
67
  log_warn("generated_at payload is missing from the JWT token. Cannot continue")
45
- context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
68
+ return nil
46
69
  end
47
70
 
48
71
  expires_time = begin
49
- time = Time.at(generated_at)
50
- time + CommandTower.config.jwt.ttl
51
- rescue
72
+ Time.at(generated_at) + CommandTower.config.jwt.ttl
73
+ rescue StandardError
52
74
  nil
53
75
  end
54
76
 
55
77
  if expires_time.nil?
56
78
  log_warn("generated_at payload cannot be parsed. Cannot continue")
57
- context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
79
+ return nil
58
80
  end
59
81
 
60
82
  if expires_time < Time.now
61
83
  log_warn("generated_at is no longer valid. Must request new token")
62
- context.fail!(msg: "Unauthorized Access. Invalid Authorization token")
84
+ return nil
63
85
  end
64
86
 
65
87
  expires_time
66
88
  end
67
89
 
68
- def email_validation_required!(user:)
69
- return unless CommandTower.config.login.plain_text.email_verify?
90
+ def email_validation_required?(user:)
91
+ return false unless CommandTower.config.login.plain_text.email_verify?
70
92
 
71
93
  if bypass_email_validation
72
94
  log_info("Bypassing email validation without checking if user should be able to continue")
73
- return
95
+ return false
74
96
  end
75
97
 
76
- return if user.email_validated
98
+ return false if user.email_validated
77
99
 
78
100
  log_info("User's email is not yet validated.")
79
- result = CommandTower::LoginStrategy::PlainText::EmailVerification::Required.(user:)
80
101
 
81
- if result.required
82
- context.fail!(msg: "User's Email must be validated before they can continue")
83
- end
102
+ CommandTower::LoginStrategy::PlainText::EmailVerification::Required.(user:).required?
103
+ end
104
+
105
+ def log_info(msg)
106
+ Rails.logger.info { "[#{self.class.name}] #{msg}" }
107
+ end
108
+
109
+ def log_warn(msg)
110
+ Rails.logger.warn { "[#{self.class.name}] #{msg}" }
84
111
  end
85
112
  end
86
113
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower::Jwt
4
+ # Narrow value object describing the result of AuthenticateUser only.
5
+ # This is deliberately not a platform result type — do not reuse it elsewhere.
6
+ class AuthenticationOutcome
7
+ attr_reader :user, :expires_at, :generated_token, :status, :msg
8
+
9
+ def self.success(user:, expires_at:, generated_token: nil)
10
+ new(success: true, user:, expires_at:, generated_token:)
11
+ end
12
+
13
+ # `user` is carried on failures so callers can distinguish a known user who
14
+ # is blocked (e.g. email verification required) from an unidentified caller.
15
+ def self.failure(msg:, status: nil, user: nil)
16
+ new(success: false, msg:, status:, user:)
17
+ end
18
+
19
+ def initialize(success:, user: nil, expires_at: nil, generated_token: nil, status: nil, msg: nil)
20
+ @success = success
21
+ @user = user
22
+ @expires_at = expires_at
23
+ @generated_token = generated_token
24
+ @status = status
25
+ @msg = msg
26
+ end
27
+
28
+ def success?
29
+ @success
30
+ end
31
+
32
+ def failure?
33
+ !@success
34
+ end
35
+ end
36
+ end
@@ -3,19 +3,38 @@
3
3
  require "jwt"
4
4
 
5
5
  module CommandTower::Jwt
6
- class Decode < CommandTower::ServiceBase
6
+ class Decode
7
+ ALGORITHM = "HS256"
7
8
 
8
- validate :token, is_a: String, required: true, sensitive: true
9
+ DecodeOutcome = Data.define(:success, :payload, :headers, :msg) do
10
+ def self.success(payload:, headers: nil)
11
+ new(success: true, payload:, headers:, msg: nil)
12
+ end
9
13
 
10
- def call
11
- data = JWT.decode(token, CommandTower.config.jwt.hmac_secret, true, { algorithm: "HS256" })
14
+ def self.failure(msg:)
15
+ new(success: false, payload: nil, headers: nil, msg:)
16
+ end
12
17
 
13
- context.payload = data[0].with_indifferent_access
14
- context.headers = data[1].with_indifferent_access
18
+ def success?
19
+ success
20
+ end
21
+
22
+ def failure?
23
+ !success
24
+ end
25
+ end
26
+
27
+ def self.call(token:)
28
+ data = JWT.decode(token, CommandTower.config.jwt.hmac_secret, true, { algorithm: ALGORITHM })
29
+
30
+ DecodeOutcome.success(
31
+ payload: data[0].with_indifferent_access,
32
+ headers: data[1].with_indifferent_access
33
+ )
15
34
  rescue JWT::DecodeError => e
16
- log_error(e)
35
+ Rails.logger.warn { "[#{name}] Failed to decode token: #{e.class.name}" }
17
36
 
18
- context.fail!(msg: "Invalid Token")
37
+ DecodeOutcome.failure(msg: "Invalid Token")
19
38
  end
20
39
  end
21
40
  end
@@ -3,13 +3,11 @@
3
3
  require "jwt"
4
4
 
5
5
  module CommandTower::Jwt
6
- class Encode < CommandTower::ServiceBase
6
+ class Encode
7
+ ALGORITHM = "HS256"
7
8
 
8
- validate :payload, is_a: Hash, required: true
9
- validate :header, is_a: Hash, required: false
10
-
11
- def call
12
- context.token = JWT.encode(payload, CommandTower.config.jwt.hmac_secret, "HS256", header || {})
9
+ def self.call(payload:, header: nil)
10
+ JWT.encode(payload, CommandTower.config.jwt.hmac_secret, ALGORITHM, header || {})
13
11
  end
14
12
  end
15
13
  end
@@ -1,21 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommandTower::Jwt
4
- class LoginCreate < CommandTower::ServiceBase
5
- on_argument_validation :fail_early
4
+ class LoginCreate
5
+ IssuedToken = Data.define(:token)
6
6
 
7
- validate :user, is_a: User, required: true
8
-
9
- def call
10
- context.token = Encode.(payload:).token
7
+ def self.call(user:)
8
+ IssuedToken.new(token: Encode.(payload: payload_for(user)))
11
9
  end
12
10
 
13
- def payload
11
+ def self.payload_for(user)
14
12
  {
15
13
  generated_at: Time.now.to_i,
16
14
  user_id: user.id,
17
15
  verifier_token: user.retreive_verifier_token!,
18
16
  }
19
17
  end
18
+ private_class_method :payload_for
20
19
  end
21
20
  end
@@ -1,20 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CommandTower::LoginStrategy::PlainText::EmailVerification
4
- class Required < CommandTower::ServiceBase
5
- validate :user, is_a: User, required: true
6
-
7
- def call
8
- context.reqired_after_time = reqired_after_time
9
- context.required = Time.now > reqired_after_time
4
+ # Decides whether an unverified account has passed its grace period and must
5
+ # verify before it can keep authenticating.
6
+ class Required
7
+ Requirement = Data.define(:required, :required_after_time) do
8
+ def required? = required
10
9
  end
11
10
 
12
- def reqired_after_time
13
- user.created_at + email_verify.verify_email_required_within
11
+ def self.call(user:)
12
+ required_after_time = user.created_at + email_verify.verify_email_required_within
13
+
14
+ Requirement.new(required: Time.now > required_after_time, required_after_time:)
14
15
  end
15
16
 
16
- def email_verify
17
+ def self.email_verify
17
18
  CommandTower.config.login.plain_text.email_verify
18
19
  end
20
+ private_class_method :email_verify
19
21
  end
20
22
  end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ ChannelDeliveryResult = Data.define(:id, :channel_key, :status) do
7
+ def self.build(id:, channel_key:, status:)
8
+ new(id:, channel_key: channel_key.to_s, status: status.to_s).freeze
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ # Domain orchestrator under Produce: validate → idempotency → plan → persist → schedule handoff.
7
+ # Not an ApplicationWorkflow (nested AW under Produce/host workflows is forbidden).
8
+ class Coordinator
9
+ def self.call(**kwargs)
10
+ new(**kwargs).call
11
+ end
12
+
13
+ def initialize(
14
+ recipient_id:,
15
+ notification_type_key:,
16
+ host_event_identity:,
17
+ title:,
18
+ body:,
19
+ metadata: nil,
20
+ preference_state: nil,
21
+ platform_enabled_channels:,
22
+ message_overrides: nil
23
+ )
24
+ @recipient_id = recipient_id
25
+ @notification_type_key = notification_type_key
26
+ @host_event_identity = host_event_identity
27
+ @title = title
28
+ @body = body
29
+ @metadata = metadata
30
+ @preference_state = preference_state
31
+ @platform_enabled_channels = platform_enabled_channels
32
+ @message_overrides = message_overrides
33
+ end
34
+
35
+ def call
36
+ request = {
37
+ recipient_id: @recipient_id,
38
+ notification_type_key: @notification_type_key,
39
+ host_event_identity: @host_event_identity,
40
+ title: @title,
41
+ body: @body,
42
+ metadata: @metadata,
43
+ preference_state: @preference_state,
44
+ platform_enabled_channels: @platform_enabled_channels,
45
+ message_overrides: @message_overrides,
46
+ }
47
+
48
+ OperationLogger.around(request:) do
49
+ validate_request!
50
+ fingerprint = RequestNormalizer.fingerprint(
51
+ title: @title,
52
+ body: @body,
53
+ metadata: @metadata,
54
+ preference_state: @preference_state,
55
+ platform_enabled_channels: @platform_enabled_channels,
56
+ message_overrides: @message_overrides,
57
+ )
58
+
59
+ existing = find_by_namespace
60
+ if existing
61
+ return resolve_existing(existing, fingerprint)
62
+ end
63
+
64
+ plan = plan!
65
+ persist_new!(request:, plan:, fingerprint:)
66
+ end
67
+ end
68
+
69
+ private
70
+
71
+ def validate_request!
72
+ errors = []
73
+ errors << "recipient_id is required" if blank?(@recipient_id)
74
+ errors << "notification_type_key is required" if blank?(@notification_type_key)
75
+ errors << "host_event_identity is required" if blank?(@host_event_identity)
76
+ errors << "title is required" if blank?(@title)
77
+ errors << "body is required" if blank?(@body)
78
+ if @platform_enabled_channels.nil?
79
+ errors << "platform_enabled_channels is required"
80
+ end
81
+ unless @preference_state.nil?
82
+ errors << "preference_state must not be supplied; CommandTower loads stored preferences"
83
+ end
84
+
85
+ raise ValidationError, errors.join("; ") if errors.any?
86
+ end
87
+
88
+ def blank?(value)
89
+ value.nil? || (value.respond_to?(:empty?) && value.empty?) ||
90
+ (value.is_a?(String) && value.strip.empty?)
91
+ end
92
+
93
+ def find_by_namespace
94
+ Messaging::Communication.find_by_idempotency_namespace(
95
+ user_id: @recipient_id,
96
+ notification_type_key: @notification_type_key,
97
+ host_event_identity: @host_event_identity,
98
+ )
99
+ end
100
+
101
+ def resolve_existing(communication, fingerprint)
102
+ if communication.accept_request_fingerprint == fingerprint
103
+ return Result.from_communication(
104
+ reload_aggregate(communication.id),
105
+ idempotent_replay: true,
106
+ )
107
+ end
108
+
109
+ raise IdempotencyConflictError,
110
+ "idempotency namespace already accepted with a different request"
111
+ end
112
+
113
+ def plan!
114
+ Planner.plan(
115
+ notification_type_key: @notification_type_key,
116
+ recipient_id: @recipient_id,
117
+ preference_state: nil,
118
+ platform_enabled_channels: @platform_enabled_channels,
119
+ message_overrides: @message_overrides,
120
+ )
121
+ rescue Planner::UnknownTypeError => e
122
+ raise UnknownTypeError, e.message
123
+ rescue Planner::IllegalOverrideError => e
124
+ raise IllegalOverrideError, e.message
125
+ rescue Planner::ImpossibleMandatoryPlanError => e
126
+ raise ImpossibleMandatoryPlanError, e.message
127
+ rescue Planner::InvalidEvaluationError => e
128
+ raise InvalidPreferenceError, e.message
129
+ rescue Preferences::InvalidPreferenceStateError => e
130
+ raise InvalidPreferenceError, e.message
131
+ rescue Preferences::StoreError => e
132
+ raise PersistenceError, e.message
133
+ rescue Preferences::UnknownTypeError => e
134
+ raise UnknownTypeError, e.message
135
+ end
136
+
137
+ def persist_new!(request:, plan:, fingerprint:)
138
+ communication = Persister.call(request:, plan:, fingerprint:)
139
+ HandoffScheduler.schedule_after_commit(communication.id)
140
+ Result.from_communication(communication, idempotent_replay: false)
141
+ rescue ActiveRecord::RecordNotUnique => error
142
+ resolve_record_not_unique!(error, fingerprint)
143
+ rescue ActiveRecord::RecordInvalid => e
144
+ raise PersistenceError, e.message
145
+ end
146
+
147
+ def resolve_record_not_unique!(error, fingerprint)
148
+ existing = find_by_namespace
149
+ if existing
150
+ return resolve_existing(existing, fingerprint)
151
+ end
152
+
153
+ raise PersistenceError, "persistence uniqueness failure"
154
+ end
155
+
156
+ def reload_aggregate(id)
157
+ Messaging::Communication.includes(
158
+ :destination_plan,
159
+ :inbox_item,
160
+ :channel_deliveries,
161
+ ).find(id)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class Error < CommandTower::Error; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ ExcludedDestination = Data.define(:destination, :reason_class) do
7
+ def self.build(destination:, reason_class:)
8
+ dest =
9
+ if destination == :inbox || destination.to_s == "inbox"
10
+ :inbox
11
+ else
12
+ destination.to_s
13
+ end
14
+
15
+ new(destination: dest, reason_class: reason_class.to_s).freeze
16
+ end
17
+
18
+ def sort_key
19
+ destination == :inbox ? "inbox" : destination.to_s
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class HandoffScheduler
7
+ def self.schedule_after_commit(communication_id)
8
+ new(communication_id).schedule_after_commit
9
+ end
10
+
11
+ def self.enqueue(communication_id)
12
+ new(communication_id).enqueue
13
+ end
14
+
15
+ def initialize(communication_id)
16
+ @communication_id = communication_id
17
+ end
18
+
19
+ def schedule_after_commit
20
+ ActiveRecord.after_all_transactions_commit { enqueue }
21
+ end
22
+
23
+ def enqueue
24
+ Messaging::HandoffJob.perform_later(@communication_id)
25
+ communication = Messaging::Communication.find_by(id: @communication_id)
26
+ if communication
27
+ Handoff::OperationLogger.scheduled(communication:)
28
+ end
29
+ rescue StandardError => error
30
+ Handoff::OperationLogger.enqueue_failed(
31
+ communication_id: @communication_id,
32
+ error:,
33
+ )
34
+ # Leave execution_handoff_status as pending — recovery will redrive.
35
+ nil
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class IdempotencyConflictError < Error; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class IllegalOverrideError < Error; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class ImpossibleMandatoryPlanError < Error; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class InvalidPreferenceError < Error; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module Messaging
5
+ module Accept
6
+ class InvariantError < Error; end
7
+ end
8
+ end
9
+ end