velocity-python 0.2.8__tar.gz → 0.2.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (411) hide show
  1. {velocity_python-0.2.8/src/velocity_python.egg-info → velocity_python-0.2.9}/PKG-INFO +1 -1
  2. {velocity_python-0.2.8 → velocity_python-0.2.9}/pyproject.toml +1 -1
  3. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/__init__.py +1 -1
  4. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/authz.py +48 -0
  5. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/security.py +105 -29
  6. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/data_service.py +35 -1
  7. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/tiles_actions.py +8 -1
  8. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/schema.py +3 -1
  9. {velocity_python-0.2.8 → velocity_python-0.2.9/src/velocity_python.egg-info}/PKG-INFO +1 -1
  10. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity_python.egg-info/SOURCES.txt +1 -0
  11. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_ensure_infrastructure.py +1 -0
  12. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_security_capability.py +77 -0
  13. velocity_python-0.2.9/tests/test_security_events_table_gate.py +210 -0
  14. {velocity_python-0.2.8 → velocity_python-0.2.9}/LICENSE +0 -0
  15. {velocity_python-0.2.8 → velocity_python-0.2.9}/README.md +0 -0
  16. {velocity_python-0.2.8 → velocity_python-0.2.9}/setup.cfg +0 -0
  17. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/__init__.py +0 -0
  18. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/amplify.py +0 -0
  19. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/assets/__init__.py +0 -0
  20. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/assets/backfill.py +0 -0
  21. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/assets/indexing.py +0 -0
  22. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/assets/references.py +0 -0
  23. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/assets/service.py +0 -0
  24. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/assets/usage_index.py +0 -0
  25. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/blobs.py +0 -0
  26. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/__init__.py +0 -0
  27. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/base.py +0 -0
  28. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/dispatch.py +0 -0
  29. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/infra.py +0 -0
  30. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/jobs.py +0 -0
  31. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/provisioning.py +0 -0
  32. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/retention.py +0 -0
  33. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/capabilities/single_session.py +0 -0
  34. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/__init__.py +0 -0
  35. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/ci_gate.py +0 -0
  36. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/config.py +0 -0
  37. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/frontend_checks.py +0 -0
  38. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/operations.py +0 -0
  39. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/policies.py +0 -0
  40. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/reconcile.py +0 -0
  41. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/deploy/targets.py +0 -0
  42. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/dirty_pipeline.py +0 -0
  43. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/email.py +0 -0
  44. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/facets.py +0 -0
  45. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/failures.py +0 -0
  46. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/feature_flags.py +0 -0
  47. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/__init__.py +0 -0
  48. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/base_handler.py +0 -0
  49. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/context.py +0 -0
  50. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/context_factory.py +0 -0
  51. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/exceptions.py +0 -0
  52. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/lambda_handler.py +0 -0
  53. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/masquerade.py +0 -0
  54. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
  55. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/action_names.py +0 -0
  56. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/audit_trail.py +0 -0
  57. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/client_errors.py +0 -0
  58. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/client_input.py +0 -0
  59. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/job_handler.py +0 -0
  60. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/permissions_actions.py +0 -0
  61. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/row_scoped.py +0 -0
  62. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/session_control.py +0 -0
  63. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/mixins/web_handler.py +0 -0
  64. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/perf.py +0 -0
  65. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/response.py +0 -0
  66. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/handlers/sqs_handler.py +0 -0
  67. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/identity.py +0 -0
  68. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/job_lifecycle.py +0 -0
  69. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/job_results.py +0 -0
  70. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/mirror.py +0 -0
  71. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/ops.py +0 -0
  72. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/permissions.py +0 -0
  73. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/queue_defaults.py +0 -0
  74. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/s3.py +0 -0
  75. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/sms.py +0 -0
  76. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/ssm_config.py +0 -0
  77. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/__init__.py +0 -0
  78. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_base_handler_error_response.py +0 -0
  79. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_least_privilege.py +0 -0
  80. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_policies.py +0 -0
  81. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_policy_size.py +0 -0
  82. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_deploy_reconcile.py +0 -0
  83. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_email.py +0 -0
  84. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
  85. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_response.py +0 -0
  86. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tests/test_sms.py +0 -0
  87. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/tiles.py +0 -0
  88. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/aws/user_data.py +0 -0
  89. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/cache.py +0 -0
  90. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/__init__.py +0 -0
  91. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/__init__.py +0 -0
  92. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/async_support.py +0 -0
  93. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/column.py +0 -0
  94. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/database.py +0 -0
  95. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/decorators.py +0 -0
  96. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/engine.py +0 -0
  97. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/jsonproxy.py +0 -0
  98. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/lazy_engine.py +0 -0
  99. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/result.py +0 -0
  100. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/row.py +0 -0
  101. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/sequence.py +0 -0
  102. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/table.py +0 -0
  103. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/transaction.py +0 -0
  104. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/core/view.py +0 -0
  105. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/exceptions.py +0 -0
  106. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/fragments.py +0 -0
  107. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/maintenance.py +0 -0
  108. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/migrations.py +0 -0
  109. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/__init__.py +0 -0
  110. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/base/__init__.py +0 -0
  111. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/base/initializer.py +0 -0
  112. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/base/operators.py +0 -0
  113. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/base/sql.py +0 -0
  114. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/base/types.py +0 -0
  115. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/__init__.py +0 -0
  116. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/operators.py +0 -0
  117. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/reserved.py +0 -0
  118. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/sql.py +0 -0
  119. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/mysql/types.py +0 -0
  120. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/__init__.py +0 -0
  121. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/operators.py +0 -0
  122. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/reserved.py +0 -0
  123. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/sql.py +0 -0
  124. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/postgres/types.py +0 -0
  125. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/__init__.py +0 -0
  126. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/operators.py +0 -0
  127. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/reserved.py +0 -0
  128. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/sql.py +0 -0
  129. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlite/types.py +0 -0
  130. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
  131. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/operators.py +0 -0
  132. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
  133. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/sql.py +0 -0
  134. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/sqlserver/types.py +0 -0
  135. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/servers/tablehelper.py +0 -0
  136. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/__init__.py +0 -0
  137. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/common_db_test.py +0 -0
  138. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/__init__.py +0 -0
  139. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/common.py +0 -0
  140. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/conftest.py +0 -0
  141. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_column.py +0 -0
  142. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_connections.py +0 -0
  143. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_database.py +0 -0
  144. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_engine.py +0 -0
  145. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
  146. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_imports.py +0 -0
  147. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_result.py +0 -0
  148. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_row.py +0 -0
  149. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
  150. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
  151. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
  152. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
  153. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
  154. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_table.py +0 -0
  155. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_table_comprehensive.py +0 -0
  156. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
  157. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/sql/__init__.py +0 -0
  158. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/sql/common.py +0 -0
  159. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
  160. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
  161. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
  162. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_db_utils.py +0 -0
  163. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_postgres.py +0 -0
  164. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
  165. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
  166. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_result_caching.py +0 -0
  167. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
  168. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
  169. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
  170. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
  171. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_sql_builder.py +0 -0
  172. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_tablehelper.py +0 -0
  173. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tests/test_view_helper.py +0 -0
  174. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/tunnel.py +0 -0
  175. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/db/utils.py +0 -0
  176. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/logging.py +0 -0
  177. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/__init__.py +0 -0
  178. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/conv/__init__.py +0 -0
  179. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/conv/iconv.py +0 -0
  180. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/conv/oconv.py +0 -0
  181. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/db.py +0 -0
  182. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/export.py +0 -0
  183. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/format.py +0 -0
  184. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/mail.py +0 -0
  185. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/merge.py +0 -0
  186. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/pdf.py +0 -0
  187. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/__init__.py +0 -0
  188. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_db.py +0 -0
  189. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_fix.py +0 -0
  190. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_format.py +0 -0
  191. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_iconv.py +0 -0
  192. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_mail_send.py +0 -0
  193. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_merge.py +0 -0
  194. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_oconv.py +0 -0
  195. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_original_error.py +0 -0
  196. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tests/test_timer.py +0 -0
  197. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/timer.py +0 -0
  198. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/misc/tools.py +0 -0
  199. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/__init__.py +0 -0
  200. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/core/__init__.py +0 -0
  201. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/core/capabilities.py +0 -0
  202. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/core/store.py +0 -0
  203. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/core/where.py +0 -0
  204. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/exceptions.py +0 -0
  205. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/__init__.py +0 -0
  206. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/base/__init__.py +0 -0
  207. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/cassandra/__init__.py +0 -0
  208. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/cassandra/translation.py +0 -0
  209. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/couchbase/__init__.py +0 -0
  210. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/couchbase/translation.py +0 -0
  211. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/dynamodb/__init__.py +0 -0
  212. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/dynamodb/translation.py +0 -0
  213. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/elasticsearch/__init__.py +0 -0
  214. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/elasticsearch/translation.py +0 -0
  215. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/memory/__init__.py +0 -0
  216. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/mongodb/__init__.py +0 -0
  217. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/mongodb/translation.py +0 -0
  218. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/nosql/servers/redis/__init__.py +0 -0
  219. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/__init__.py +0 -0
  220. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/_mirror_core.py +0 -0
  221. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/authorizenet_adapter.py +0 -0
  222. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/authorizenet_mirror.py +0 -0
  223. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/base_adapter.py +0 -0
  224. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/braintree_adapter.py +0 -0
  225. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/braintree_mirror.py +0 -0
  226. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/charge_rules.py +0 -0
  227. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/money.py +0 -0
  228. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/plaid_api.py +0 -0
  229. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/plaid_webhook.py +0 -0
  230. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/redact.py +0 -0
  231. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/schema.py +0 -0
  232. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/stripe_adapter.py +0 -0
  233. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/payment/stripe_mirror.py +0 -0
  234. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/testing/__init__.py +0 -0
  235. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity/testing/fake_db.py +0 -0
  236. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity_python.egg-info/dependency_links.txt +0 -0
  237. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity_python.egg-info/entry_points.txt +0 -0
  238. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity_python.egg-info/requires.txt +0 -0
  239. {velocity_python-0.2.8 → velocity_python-0.2.9}/src/velocity_python.egg-info/top_level.txt +0 -0
  240. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_access_error_403.py +0 -0
  241. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_action_names.py +0 -0
  242. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_activity_tracking_aborted_tx.py +0 -0
  243. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_amplify_redirects.py +0 -0
  244. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_asset_indexing.py +0 -0
  245. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_asset_references.py +0 -0
  246. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_assets_service.py +0 -0
  247. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_async_support.py +0 -0
  248. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_audit_mixin.py +0 -0
  249. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_authz.py +0 -0
  250. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_aws_package_hygiene.py +0 -0
  251. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_batch_operations.py +0 -0
  252. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_blob_store.py +0 -0
  253. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_business_objects.py +0 -0
  254. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_capabilities.py +0 -0
  255. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_capability_catalog.py +0 -0
  256. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_check_changelog.py +0 -0
  257. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_ci_gate.py +0 -0
  258. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_client_errors_mixin.py +0 -0
  259. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_client_input_contract_export.py +0 -0
  260. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_client_sql_boundary.py +0 -0
  261. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_client_table_gate_case_and_pointers.py +0 -0
  262. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_cognito_client_lazy.py +0 -0
  263. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_cognito_user_cache.py +0 -0
  264. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_column_tx_arg.py +0 -0
  265. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_concurrency_safety.py +0 -0
  266. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_connection_pool.py +0 -0
  267. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_connection_resilience.py +0 -0
  268. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_context_flags_loader.py +0 -0
  269. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_context_job_descriptions.py +0 -0
  270. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_context_queue_job.py +0 -0
  271. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_conv_combine.py +0 -0
  272. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_data_service_owner_scope.py +0 -0
  273. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_data_service_query_cap.py +0 -0
  274. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_data_service_renumber.py +0 -0
  275. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_data_service_row_number.py +0 -0
  276. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_data_service_save_as.py +0 -0
  277. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_data_service_security_gates.py +0 -0
  278. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_db_credentials_ssm_cascade.py +0 -0
  279. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_db_maintenance.py +0 -0
  280. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_db_tunnel.py +0 -0
  281. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_declared_column_types.py +0 -0
  282. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_decorators.py +0 -0
  283. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_delete_hook_fk_blocked.py +0 -0
  284. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_deploy_config_fields.py +0 -0
  285. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_deploy_engine.py +0 -0
  286. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_deploy_frontend_checks.py +0 -0
  287. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_deploy_operations_queue.py +0 -0
  288. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_deploy_policies.py +0 -0
  289. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_dirty_pipeline_fast_path.py +0 -0
  290. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_dlq_constant.py +0 -0
  291. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_docs_snippets.py +0 -0
  292. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_email_processing.py +0 -0
  293. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_enqueue_metric_namespace.py +0 -0
  294. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_enqueue_send_failures.py +0 -0
  295. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_export_workbook.py +0 -0
  296. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_facets.py +0 -0
  297. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_facets_and_where.py +0 -0
  298. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_failures.py +0 -0
  299. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_feature_flags.py +0 -0
  300. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_get_cognito_user_provider.py +0 -0
  301. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_http_handler_rollback.py +0 -0
  302. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_iconv_money_to_cents.py +0 -0
  303. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_identifier_injection_guard.py +0 -0
  304. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_identity.py +0 -0
  305. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_job_handler.py +0 -0
  306. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_job_lifecycle.py +0 -0
  307. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_job_results.py +0 -0
  308. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_json_columns.py +0 -0
  309. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_jsonb_dict_adapter.py +0 -0
  310. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_lambda_handler.py +0 -0
  311. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_lambda_handler_auth.py +0 -0
  312. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_lambda_handler_masquerade.py +0 -0
  313. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_lambda_handler_user_cache.py +0 -0
  314. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_lazy_engine.py +0 -0
  315. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_masquerade_grant.py +0 -0
  316. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_merge_on_conflict.py +0 -0
  317. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_mirror.py +0 -0
  318. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_mirror_upsert_public.py +0 -0
  319. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_mixins_import.py +0 -0
  320. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_n_plus_one.py +0 -0
  321. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_nosql_dynamodb.py +0 -0
  322. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_nosql_memory.py +0 -0
  323. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_nosql_mongodb.py +0 -0
  324. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_nosql_redis.py +0 -0
  325. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_nosql_translation.py +0 -0
  326. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_nosql_where.py +0 -0
  327. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_observability.py +0 -0
  328. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_on_action_default.py +0 -0
  329. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_on_commit.py +0 -0
  330. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_ops.py +0 -0
  331. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_audit_fixes.py +0 -0
  332. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_authorizenet_adapter.py +0 -0
  333. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_braintree_adapter.py +0 -0
  334. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_braintree_mirror.py +0 -0
  335. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_business_agnostic.py +0 -0
  336. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_charge_reference.py +0 -0
  337. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_extra_bounds.py +0 -0
  338. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_lazy_imports.py +0 -0
  339. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_mirror_empty_table.py +0 -0
  340. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_profile_sorting.py +0 -0
  341. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_payment_stripe_adapter.py +0 -0
  342. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pdf.py +0 -0
  343. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_permissions.py +0 -0
  344. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_permissions_actions.py +0 -0
  345. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_permissions_notices.py +0 -0
  346. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_permissions_review.py +0 -0
  347. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_bulk_cache.py +0 -0
  348. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_capability_types.py +0 -0
  349. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_ensure_infrastructure.py +0 -0
  350. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_get_tables.py +0 -0
  351. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_permissions.py +0 -0
  352. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_pointer_joins.py +0 -0
  353. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_row_scope.py +0 -0
  354. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_search_export.py +0 -0
  355. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_tiles.py +0 -0
  356. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pgserver_user_data.py +0 -0
  357. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_plaid_api.py +0 -0
  358. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_plaid_webhook.py +0 -0
  359. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_pointer_join_sql.py +0 -0
  360. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_postgres_initializer_config.py +0 -0
  361. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_postgres_py_type.py +0 -0
  362. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_prepared_statements.py +0 -0
  363. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_psycopg3_upgrade.py +0 -0
  364. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_query_cache.py +0 -0
  365. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_query_missing_table_unlocked.py +0 -0
  366. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_raw_sql_prefix_policy.py +0 -0
  367. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_read_cache.py +0 -0
  368. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_readme_imports.py +0 -0
  369. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_response.py +0 -0
  370. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_restricted_direct_tables.py +0 -0
  371. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_result_returning.py +0 -0
  372. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_retry_side_effect_guard.py +0 -0
  373. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_return_default_safety.py +0 -0
  374. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_row_batch_update.py +0 -0
  375. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_row_cache_staleness.py +0 -0
  376. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_row_scoped_data_service.py +0 -0
  377. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_s3_bucket_config.py +0 -0
  378. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_s3_list_client_files.py +0 -0
  379. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_s3_upload.py +0 -0
  380. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_schema_lock_config_other_backends.py +0 -0
  381. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_schema_migrations.py +0 -0
  382. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_search_paging_export.py +0 -0
  383. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_security_hardening.py +0 -0
  384. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_server_cursor.py +0 -0
  385. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_session_control.py +0 -0
  386. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_single_autocommit_safety.py +0 -0
  387. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_single_session_capability.py +0 -0
  388. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_sms_twilio_signature.py +0 -0
  389. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_spreadsheet_functions.py +0 -0
  390. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_sqlite_backend.py +0 -0
  391. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_sqs_per_record_transactions.py +0 -0
  392. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_ssm_config.py +0 -0
  393. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_ssm_config_require.py +0 -0
  394. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_store_user_data.py +0 -0
  395. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_sys_modified_count_postgres_demo.py +0 -0
  396. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_table_alter.py +0 -0
  397. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_table_claim.py +0 -0
  398. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_table_find_cache.py +0 -0
  399. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_table_purge.py +0 -0
  400. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_testing_fake_db.py +0 -0
  401. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_tiles.py +0 -0
  402. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_transaction_class_wrapping.py +0 -0
  403. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_transaction_commit_and_ownership.py +0 -0
  404. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_transaction_edge_cases.py +0 -0
  405. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_tx_lifecycle_retry.py +0 -0
  406. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_tx_read_cache_and_bulk.py +0 -0
  407. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_user_data.py +0 -0
  408. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_user_not_provisioned.py +0 -0
  409. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_where_clause_validation.py +0 -0
  410. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_write_hook_create_flow.py +0 -0
  411. {velocity_python-0.2.8 → velocity_python-0.2.9}/tests/test_write_hook_empty_strings.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "velocity-python"
7
- version = "0.2.8"
7
+ version = "0.2.9"
8
8
  authors = [
9
9
  { name="Velocity Team", email="info@codeclubs.org" },
10
10
  ]
@@ -1,4 +1,4 @@
1
- __version__ = version = "0.2.8"
1
+ __version__ = version = "0.2.9"
2
2
 
3
3
  import importlib as _importlib
4
4
 
@@ -935,6 +935,33 @@ def is_protected(table_name):
935
935
  return table_name in _PROTECTED
936
936
 
937
937
 
938
+ # Tables whose rows are confidential (the security audit: everyone's sign-ins,
939
+ # addresses, refusals). The generic Data actions READ them only on a handler
940
+ # that lists the table in ``confidential_reads`` (an explicit opt-in, e.g. an
941
+ # operators-only console), in every mode: ``gate`` records nothing, so even a
942
+ # permitted read there would be unaudited.
943
+ _CONFIDENTIAL = set()
944
+
945
+
946
+ def confidential_tables(*names):
947
+ """Mark tables generic Data reads refuse unless the handler opts in (see
948
+ ``DataServiceMixin.confidential_reads``), whatever ``authz_mode`` says.
949
+ Combine with :func:`protect_tables` to refuse writes as well."""
950
+ _CONFIDENTIAL.update(name for name in names if isinstance(name, str) and name)
951
+
952
+
953
+ def is_confidential(table_name):
954
+ return table_name in _CONFIDENTIAL
955
+
956
+
957
+ # velocity's own audit sink (capabilities.security.EVENTS_TABLE): append-only
958
+ # and confidential on every site, registered here so the rule never depends on
959
+ # which modules a handler happened to import (VelocityAudit SEC-12 follow-up).
960
+ SECURITY_EVENTS_TABLE = "sys_security_events"
961
+ protect_tables(SECURITY_EVENTS_TABLE)
962
+ confidential_tables(SECURITY_EVENTS_TABLE)
963
+
964
+
938
965
  # ── the action index ───────────────────────────────────────────────────────
939
966
 
940
967
  # id(rules tuple) -> (the tuple, {key: its rules in order}), where key is the
@@ -1319,6 +1346,15 @@ def _describe(resource):
1319
1346
  return f"{resource.type}:{resource.id}"
1320
1347
 
1321
1348
 
1349
+ def record_confidential_read(handler, tx, context, table):
1350
+ """Record one generic read of a confidential table by a handler that opted
1351
+ in (``confidential_reads``) as ``privileged_access``; False when it could
1352
+ not be recorded (the caller refuses the read). The opt-in serves an
1353
+ operators-only console; every use of it is still on the record."""
1354
+ decision = Decision(True, rule="confidential_reads", audit=True, action=DATA_READ)
1355
+ return bool(_record(handler, tx, context, decision, Resource(TABLE, table)))
1356
+
1357
+
1322
1358
  def _record(handler, tx, context, decision, resource):
1323
1359
  # imported here: the capabilities package pulls in more than a pure
1324
1360
  # decision module should load at import
@@ -1330,6 +1366,16 @@ def _record(handler, tx, context, decision, resource):
1330
1366
  if isinstance(session.get("masquerade"), dict)
1331
1367
  else None
1332
1368
  ) or session.get("email_address")
1369
+ # the subject API Gateway's authorizer verified (the real caller, also
1370
+ # under masquerade), and its issuer, so the record names an identity and
1371
+ # not only an email
1372
+ sub = session.get("sub") or None
1373
+ provider = session.get("authentication_provider")
1374
+ issuer = session.get("iss") or (
1375
+ "https://" + provider.split(",", 1)[0].strip()
1376
+ if isinstance(provider, str) and provider.strip()
1377
+ else None
1378
+ )
1333
1379
  return record_event(
1334
1380
  tx,
1335
1381
  PRIVILEGED_ACCESS,
@@ -1337,4 +1383,6 @@ def _record(handler, tx, context, decision, resource):
1337
1383
  aws_event=getattr(handler, "aws_event", None),
1338
1384
  context=context,
1339
1385
  user_email=actor,
1386
+ user_sub=sub,
1387
+ detail={"iss": issuer} if issuer else None,
1340
1388
  )
@@ -20,6 +20,15 @@ Attach :class:`SecurityMixin` FIRST in a handler's bases; it runs in
20
20
  ``beforeAction`` ahead of auth and every action, and is a no-op unless the
21
21
  site's ``sys_capabilities`` row for ``security`` is enabled. A refusal raises
22
22
  :class:`AccessError` (a 403) after committing a ``sys_security_events`` row.
23
+
24
+ ``sys_security_events`` itself is a CORE table (``SCHEMA``, in
25
+ ``velocity.aws.schema``'s ``core`` group), not the capability's: it is
26
+ velocity's one audit sink — refusals, masquerade grants, sign-ins — and every
27
+ site keeps it whether or not the guards are on (VelocityAudit SEC-12: with the
28
+ table capability-owned, a site without ``security`` silently dropped its
29
+ masquerade audit). Pruning stays here (``security_events_days``, default 365 to
30
+ match a one-year audit retention); a site without the capability lists the
31
+ table in the ``retention`` capability instead.
23
32
  """
24
33
 
25
34
  from __future__ import annotations
@@ -46,6 +55,48 @@ log = logging.getLogger(__name__)
46
55
 
47
56
  EVENTS_TABLE = "sys_security_events"
48
57
  RATE_TABLE = "sys_rate_limit"
58
+ EVENT_COLUMNS = {
59
+ "guard": "TEXT",
60
+ "reason": "TEXT",
61
+ "ip": "TEXT",
62
+ "country": "TEXT",
63
+ "path": "TEXT",
64
+ "action": "TEXT",
65
+ "user_email": "TEXT",
66
+ "user_agent": "TEXT",
67
+ "user_sub": "TEXT",
68
+ "detail": "JSONB",
69
+ # when it happened (a sign-in trigger fired), which a queue can write
70
+ # later than sys_created; defaults to the time of writing
71
+ "occurred_at": "TIMESTAMPTZ",
72
+ }
73
+ # Append-only and confidential on every site: authz registers the table as
74
+ # protected (no generic write) and confidential (generic reads need an opt-in
75
+ # or enforce) itself, so the rule holds whether or not this module is imported.
76
+ # core infrastructure (velocity.aws.schema "core"): every site has the table
77
+ SCHEMA = {
78
+ EVENTS_TABLE: {
79
+ "columns": dict(EVENT_COLUMNS),
80
+ "indexes": [
81
+ {
82
+ "columns": ["sys_created"],
83
+ "name": "idx__sys_security_events__sys_created",
84
+ },
85
+ {
86
+ "columns": ["guard", "sys_created"],
87
+ "name": "idx__sys_security_events__guard",
88
+ },
89
+ {
90
+ "columns": ["user_sub", "sys_created"],
91
+ "name": "idx__sys_security_events__user_sub",
92
+ },
93
+ {
94
+ "columns": ["user_sub", "occurred_at"],
95
+ "name": "idx__sys_security_events__user_sub_occurred",
96
+ },
97
+ ],
98
+ },
99
+ }
49
100
  CACHE_TTL_SECONDS = 60 # per-container cache of the switches + site row
50
101
 
51
102
  SWITCHES = {
@@ -59,7 +110,7 @@ SWITCHES = {
59
110
  KNOBS = {
60
111
  "security_rate_limit_per_minute": "120",
61
112
  "security_payload_max_bytes": "1000000",
62
- "security_events_days": "90",
113
+ "security_events_days": "365",
63
114
  }
64
115
 
65
116
 
@@ -275,30 +326,62 @@ def enforce(tx, aws_event, context, *, now=None):
275
326
  return config
276
327
 
277
328
 
278
- def record_event(tx, guard, reason, *, aws_event=None, context=None, user_email=None):
329
+ def record_event(
330
+ tx,
331
+ guard,
332
+ reason,
333
+ *,
334
+ aws_event=None,
335
+ context=None,
336
+ user_email=None,
337
+ user_sub=None,
338
+ detail=None,
339
+ occurred_at=None,
340
+ ):
279
341
  """Append one ``sys_security_events`` row and commit it — an audit row
280
342
  must survive whatever the caller does next (typically raise). The one
281
- audit sink for the framework: the request guards, masquerade grants, and
282
- anything else security-relevant write here. Silent when the table is not
283
- provisioned in this site, and an audit failure never breaks the request.
284
- Returns True when a row was written."""
343
+ audit sink for the framework: the request guards, masquerade grants,
344
+ sign-ins, and anything else security-relevant write here. ``user_sub`` is
345
+ the identity provider's subject; ``detail`` a JSON-able dict of facts that
346
+ have no column; ``occurred_at`` when it happened, if not now (an event
347
+ relayed through a queue). Silent when the table is not provisioned in this site, and
348
+ an audit failure never breaks the request. Returns True when a row was
349
+ written.
350
+
351
+ A table created before ``user_sub``/``detail`` existed (a site whose schema
352
+ lane has not run since) still records the row without them: on a locked
353
+ schema an unknown column would otherwise lose the whole event."""
285
354
  try:
286
355
  table = tx.table(EVENTS_TABLE)
287
356
  if not table.exists():
288
357
  return False
289
358
  facts = request_facts(aws_event)
290
- table.insert(
291
- {
292
- "guard": guard,
293
- "reason": reason,
294
- "ip": facts["ip"],
295
- "country": facts["country"],
296
- "path": facts["path"],
297
- "action": _action(context) if context is not None else "",
298
- "user_email": user_email,
299
- "user_agent": facts["user_agent"][:512],
300
- }
301
- )
359
+ row = {
360
+ "guard": guard,
361
+ "reason": reason,
362
+ "ip": facts["ip"],
363
+ "country": facts["country"],
364
+ "path": facts["path"],
365
+ "action": _action(context) if context is not None else "",
366
+ "user_email": user_email,
367
+ "user_agent": facts["user_agent"][:512],
368
+ }
369
+ extra = {
370
+ "user_sub": user_sub,
371
+ "detail": detail,
372
+ "occurred_at": occurred_at or datetime.datetime.now(datetime.timezone.utc),
373
+ }
374
+ extra = {k: v for k, v in extra.items() if v is not None}
375
+ if extra:
376
+ try:
377
+ have = {c.lower() for c in table.sys_columns()}
378
+ except Exception:
379
+ # can't tell which newer columns exist: the event still lands
380
+ # with the columns every version of the table has
381
+ log.warning("security event %s: columns unknown", guard, exc_info=True)
382
+ have = set()
383
+ row.update({k: v for k, v in extra.items() if k in have})
384
+ table.insert(row)
302
385
  tx.commit()
303
386
  return True
304
387
  except Exception:
@@ -386,16 +469,9 @@ class Security(Capability):
386
469
  ),
387
470
  )
388
471
  schema = {
389
- EVENTS_TABLE: {
390
- "guard": "TEXT",
391
- "reason": "TEXT",
392
- "ip": "TEXT",
393
- "country": "TEXT",
394
- "path": "TEXT",
395
- "action": "TEXT",
396
- "user_email": "TEXT",
397
- "user_agent": "TEXT",
398
- },
472
+ # the events table is core (module SCHEMA); listed here too so turning
473
+ # the capability on in an older site still ensures it
474
+ EVENTS_TABLE: dict(EVENT_COLUMNS),
399
475
  RATE_TABLE: {"bucket": "TEXT", "window_start": "TIMESTAMPTZ", "hits": "BIGINT"},
400
476
  }
401
477
  schedules = (("prune", HOURLY),)
@@ -407,7 +483,7 @@ class Security(Capability):
407
483
  if action != "prune":
408
484
  raise ValueError(f"security: unknown action {action!r}")
409
485
  now = datetime.datetime.now(datetime.timezone.utc)
410
- days = int(config.get("security_events_days") or 90)
486
+ days = int(config.get("security_events_days") or 365)
411
487
  return {
412
488
  "rate_buckets": tx.table(RATE_TABLE).purge(
413
489
  {"<sys_created": now - datetime.timedelta(hours=1)}
@@ -588,6 +588,7 @@ class ExportStream:
588
588
  def __enter__(self):
589
589
  handler, payload = self.handler, self.payload
590
590
  handler._assert_client_table(self.table, "obj")
591
+ handler._audit_confidential_read(self.tx, self.table, self.context)
591
592
  # ``table`` is the only source: it is what passed the table gate and
592
593
  # whose hooks and scope run below, so a payload ``obj`` naming any
593
594
  # other table is refused rather than read.
@@ -915,6 +916,11 @@ class DataServiceMixin:
915
916
  # handler, not in the gate methods' signatures, so a site's override of
916
917
  # those keeps working.
917
918
  _table_operation = authz.DATA_READ
919
+ # Confidential tables (``authz.confidential_tables``) this handler serves
920
+ # to generic reads: an explicit opt-in for a console whose every user is
921
+ # an operator. Without it such a table is never read through generic Data
922
+ # (in any authz mode); it is read through its own audited action.
923
+ confidential_reads = frozenset()
918
924
 
919
925
  def _client_table_allowed(self, table):
920
926
  # lower case only: velocity lower-cases every Table name and
@@ -937,10 +943,30 @@ class DataServiceMixin:
937
943
  # ``authz_mode`` say.
938
944
  if self._table_operation != authz.DATA_READ and authz.is_protected(table):
939
945
  return False
946
+ # Confidential tables (``authz.confidential_tables``: the security
947
+ # audit) are read through generic Data only where the handler opted
948
+ # in. Never by default, and not under ``enforce`` either: ``gate``
949
+ # answers allow/deny but records nothing, so a super user's read would
950
+ # go unaudited; the table's own action (``authz.require``) records it.
951
+ if authz.is_confidential(table) and table not in self.confidential_reads:
952
+ return False
940
953
  return authz.gate(
941
954
  self, self._table_operation, authz.Resource(authz.TABLE, table)
942
955
  )
943
956
 
957
+ def _audit_confidential_read(self, tx, table, context):
958
+ """A generic read of a confidential table (only reachable on a handler
959
+ that lists it in ``confidential_reads``) is recorded as privileged
960
+ access first, and refused when it can't be: the opt-in keeps an
961
+ operators' grid working without making its reads invisible. Called by
962
+ the read ACTIONS right after their table gate (query, read-object,
963
+ find-object, query-direct, export), never from an overridable seam,
964
+ so a site's hook override can't skip it."""
965
+ if not authz.is_confidential(table):
966
+ return
967
+ if not authz.record_confidential_read(self, tx, context, table):
968
+ raise AccessError("That access could not be recorded, so it was refused.")
969
+
944
970
  def _assert_client_table(self, table, label="table"):
945
971
  """The table gate every client action passes (SEC 4.2)."""
946
972
  ClientInputGuard.table_name(table, label)
@@ -1022,7 +1048,11 @@ class DataServiceMixin:
1022
1048
  if not info:
1023
1049
  continue
1024
1050
  foreign = info["referenced_table_name"]
1025
- if not self._client_table_allowed(foreign):
1051
+ # never into a confidential table: its reads are audited by the
1052
+ # actions that open it by name, and a join would skip that
1053
+ if authz.is_confidential(foreign) or not self._client_table_allowed(
1054
+ foreign
1055
+ ):
1026
1056
  raise AlertError(f"Access to '{table}.{local}>' is not permitted.")
1027
1057
  # A join reads the foreign table's rows without that table's row
1028
1058
  # filter. So someone who reaches only part of it cannot read it
@@ -2053,6 +2083,7 @@ class DataServiceMixin:
2053
2083
  if not obj:
2054
2084
  raise ValueError("Parameter 'object' cannot be empty")
2055
2085
  self._assert_client_table(table_name, "tableName")
2086
+ self._audit_confidential_read(tx, table_name, context)
2056
2087
  if not isinstance(obj, dict):
2057
2088
  raise BadRequestError("Parameter 'object' must be an object")
2058
2089
  if obj.get("sys_id") != NEW_ROW:
@@ -2094,6 +2125,7 @@ class DataServiceMixin:
2094
2125
  if not query or "where" not in query:
2095
2126
  raise ValueError("Parameter 'query' must contain 'where' clause")
2096
2127
  self._assert_client_table(table_name, "tableName")
2128
+ self._audit_confidential_read(tx, table_name, context)
2097
2129
  query["where"] = self._client_where(tx, table_name, query["where"])
2098
2130
 
2099
2131
  row = self.find_hook(
@@ -2254,6 +2286,7 @@ class DataServiceMixin:
2254
2286
  if not table:
2255
2287
  raise ValueError("Parameter 'obj' cannot be empty")
2256
2288
  self._assert_client_table(table, "obj")
2289
+ self._audit_confidential_read(tx, table, context)
2257
2290
  payload["params"] = self._client_query_params(tx, table, payload.get("params"))
2258
2291
 
2259
2292
  result_format = payload.get("result_format")
@@ -2438,6 +2471,7 @@ class DataServiceMixin:
2438
2471
 
2439
2472
  self._assert_direct_access_allowed(table_name)
2440
2473
  self._assert_direct_unscoped(table_name, context)
2474
+ self._audit_confidential_read(tx, table_name, context)
2441
2475
 
2442
2476
  params = self._client_query_params(tx, table_name, payload.get("params", {}))
2443
2477
 
@@ -112,6 +112,10 @@ class TilesMixin:
112
112
  rule with the viewer filled in, and that where."""
113
113
  table = tile["target"]
114
114
  self._assert_client_table(table, "target")
115
+ # A confidential table (the security audit) is never a tile target:
116
+ # a saved, re-counted filter over it would be an unaudited oracle.
117
+ if authz.is_confidential(table):
118
+ raise AlertError(f"'{table}' can't be counted on a tile.")
115
119
  rule = tiles.fill_rule(
116
120
  tiles.clean_rule(_as_dict(tile.get("rule"))),
117
121
  principal,
@@ -218,7 +222,10 @@ class TilesMixin:
218
222
  return declared is not None and (
219
223
  not declared.permission or principal.holds(declared.permission)
220
224
  )
221
- return bool(self._client_table_allowed(str(tile.get("target") or "")))
225
+ target = str(tile.get("target") or "")
226
+ if authz.is_confidential(target):
227
+ return False
228
+ return bool(self._client_table_allowed(target))
222
229
 
223
230
  def _tile_public(self, tx, context, tile, principal, today, fresh=False, version=0):
224
231
  """One tile as the browser gets it, or None when it is not this
@@ -21,7 +21,8 @@ renames or retypes an existing column.
21
21
 
22
22
  Groups:
23
23
 
24
- * ``core`` — the tables every velocity handler app writes.
24
+ * ``core`` — the tables every velocity handler app writes, including the
25
+ ``sys_security_events`` audit sink (SEC-12).
25
26
  * ``assets`` — ``images`` + ``asset_usage_index`` (:mod:`velocity.aws.assets`).
26
27
  * ``payment`` — processor mirror tables (:mod:`velocity.payment.schema`).
27
28
  * ``permissions`` — roles, grants and requests (:mod:`velocity.aws.permissions`).
@@ -50,6 +51,7 @@ SCHEMA_SOURCES: Dict[str, Tuple[str, ...]] = {
50
51
  "velocity.aws.handlers.mixins.web_handler",
51
52
  "velocity.aws.handlers.mixins.session_control",
52
53
  "velocity.aws.user_data",
54
+ "velocity.aws.capabilities.security",
53
55
  "velocity.db.migrations",
54
56
  ),
55
57
  "assets": ("velocity.aws.assets",),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: velocity-python
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: A rapid application development library for interfacing with data storage
5
5
  Author-email: Velocity Team <info@codeclubs.org>
6
6
  License-Expression: MIT
@@ -377,6 +377,7 @@ tests/test_schema_lock_config_other_backends.py
377
377
  tests/test_schema_migrations.py
378
378
  tests/test_search_paging_export.py
379
379
  tests/test_security_capability.py
380
+ tests/test_security_events_table_gate.py
380
381
  tests/test_security_hardening.py
381
382
  tests/test_server_cursor.py
382
383
  tests/test_session_control.py
@@ -74,6 +74,7 @@ def test_core_declares_every_velocity_owned_runtime_table():
74
74
  "lambda_metrics",
75
75
  "user_data",
76
76
  "sys_user_data",
77
+ "sys_security_events",
77
78
  "app_session",
78
79
  "sys_feature_flags",
79
80
  "sys_capabilities",
@@ -41,10 +41,17 @@ class FakeTable:
41
41
  return 3
42
42
 
43
43
  exists_flag = True
44
+ # sys_columns() of the live table; None = every declared events column
45
+ columns = None
44
46
 
45
47
  def exists(self):
46
48
  return self.exists_flag
47
49
 
50
+ def sys_columns(self):
51
+ if self.columns is not None:
52
+ return list(self.columns)
53
+ return ["sys_id", "sys_created", *sec.EVENT_COLUMNS]
54
+
48
55
 
49
56
  class FakeTx:
50
57
  def __init__(self):
@@ -305,3 +312,73 @@ def test_capability_declares_schema_switches_and_prunes_with_purge():
305
312
  assert "<sys_created" in tx.table(sec.EVENTS_TABLE).purges[0]
306
313
  with pytest.raises(ValueError):
307
314
  cap.handle("nope", tx, None, {})
315
+
316
+
317
+ # -- sys_security_events as velocity's core audit sink (SEC-12, Paul 2026-10-25)
318
+
319
+
320
+ def test_record_event_stores_subject_and_detail():
321
+ tx = FakeTx()
322
+ ok = sec.record_event(
323
+ tx,
324
+ "auth.login",
325
+ "signed in",
326
+ user_email="staff@example.com",
327
+ user_sub="sub-123",
328
+ detail={"client_id": "abc", "new_device": True},
329
+ )
330
+ assert ok is True and tx.commits == 1
331
+ row = tx.table(sec.EVENTS_TABLE).rows[0]
332
+ assert row["guard"] == "auth.login" and row["user_sub"] == "sub-123"
333
+ assert row["detail"] == {"client_id": "abc", "new_device": True}
334
+
335
+
336
+ def test_record_event_on_a_table_older_than_the_new_columns_still_records():
337
+ # a site whose schema lane has not added user_sub/detail yet: on a locked
338
+ # schema the unknown columns would lose the whole event, so they are left out
339
+ tx = FakeTx()
340
+ table = tx.table(sec.EVENTS_TABLE)
341
+ table.columns = ["sys_id", "guard", "reason", "ip", "user_email"]
342
+ assert sec.record_event(tx, "masquerade.start", "x", user_sub="s", detail={"a": 1})
343
+ row = table.rows[0]
344
+ assert row["guard"] == "masquerade.start"
345
+ assert "user_sub" not in row and "detail" not in row
346
+
347
+
348
+ def test_every_event_says_when_it_occurred():
349
+ tx = FakeTx()
350
+ assert sec.record_event(tx, "ip_denylist", "listed")
351
+ row = tx.table(sec.EVENTS_TABLE).rows[0]
352
+ assert row["occurred_at"].tzinfo is not None
353
+ when = datetime.datetime(2026, 10, 25, 15, tzinfo=datetime.timezone.utc)
354
+ assert sec.record_event(tx, "auth.login", "signed in", occurred_at=when)
355
+ assert tx.table(sec.EVENTS_TABLE).rows[1]["occurred_at"] == when
356
+
357
+
358
+ def test_events_table_is_core_and_retention_defaults_to_a_year():
359
+ from velocity.aws import schema as infra
360
+
361
+ core = infra.infrastructure_schema()
362
+ assert core[sec.EVENTS_TABLE]["columns"]["detail"] == "JSONB"
363
+ assert core[sec.EVENTS_TABLE]["columns"]["user_sub"] == "TEXT"
364
+ assert sec.KNOBS["security_events_days"] == "365"
365
+ # the capability still declares it, so enabling it on an older site ensures it
366
+ assert sec.Security_instance().schema[sec.EVENTS_TABLE] == sec.EVENT_COLUMNS
367
+ tx = FakeTx()
368
+ sec.Security_instance().handle("prune", tx, None, {})
369
+ cutoff = tx.table(sec.EVENTS_TABLE).purges[0]["<sys_created"]
370
+ age = datetime.datetime.now(datetime.timezone.utc) - cutoff
371
+ assert 364 < age.days <= 365
372
+
373
+
374
+ def test_an_event_still_lands_when_the_columns_cannot_be_read():
375
+ tx = FakeTx()
376
+ table = tx.table(sec.EVENTS_TABLE)
377
+
378
+ def broken():
379
+ raise RuntimeError("catalog unavailable")
380
+
381
+ table.sys_columns = broken
382
+ assert sec.record_event(tx, "masquerade", "acting as x", user_sub="s")
383
+ row = table.rows[0]
384
+ assert row["guard"] == "masquerade" and "user_sub" not in row