tba-storage-core 0.4.0.dev2__py3-none-any.whl

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 (294) hide show
  1. tba_storage_core/__init__.py +25 -0
  2. tba_storage_core/components.py +35 -0
  3. tba_storage_core/framework.py +3 -0
  4. tba_storage_core/migrations/__init__.py +15 -0
  5. tba_storage_core/migrations/contract.py +74 -0
  6. tba_storage_core/migrations/env.py +7 -0
  7. tba_storage_core/migrations/helpers.py +219 -0
  8. tba_storage_core/migrations/versions/0001_initial_identity_tables.py +17 -0
  9. tba_storage_core/migrations/versions/0002_client_and_service_tables.py +23 -0
  10. tba_storage_core/migrations/versions/0003_authorization_runtime_tables.py +17 -0
  11. tba_storage_core/migrations/versions/0004_device_par_revocation_tables.py +17 -0
  12. tba_storage_core/migrations/versions/0005_session_logout_tables.py +17 -0
  13. tba_storage_core/migrations/versions/0006_key_rotation_and_audit_tables.py +17 -0
  14. tba_storage_core/migrations/versions/0007_browser_session_cookie_and_auth_code_linkage.py +50 -0
  15. tba_storage_core/migrations/versions/0008_refresh_token_family_state.py +44 -0
  16. tba_storage_core/migrations/versions/0009_admin_identity_bootstrap_and_password_recovery.py +48 -0
  17. tba_storage_core/migrations/versions/0010_realm_namespace_tables.py +33 -0
  18. tba_storage_core/migrations/versions/0011_delegation_grant_lifecycle_tables.py +39 -0
  19. tba_storage_core/migrations/versions/0012_key_version_and_token_provenance_tables.py +36 -0
  20. tba_storage_core/migrations/versions/0013_contract_durable_state_tables.py +59 -0
  21. tba_storage_core/migrations/versions/0014_optional_contract_state_tables.py +57 -0
  22. tba_storage_core/migrations/versions/0015_principal_identity_credential_tables.py +85 -0
  23. tba_storage_core/migrations/versions/0016_crypto_key_storage_tables.py +84 -0
  24. tba_storage_core/migrations/versions/0017_authorization_server_table.py +21 -0
  25. tba_storage_core/migrations/versions/0018_policy_tables.py +27 -0
  26. tba_storage_core/migrations/versions/0019_federation_and_invariant_tables.py +35 -0
  27. tba_storage_core/migrations/versions/0020_backchannel_logout_replay_table.py +21 -0
  28. tba_storage_core/migrations/versions/0021_authentication_challenge_table.py +21 -0
  29. tba_storage_core/migrations/versions/0022_authority_and_trust_graph_tables.py +35 -0
  30. tba_storage_core/migrations/versions/0023_artifact_and_token_profile_discriminators.py +65 -0
  31. tba_storage_core/migrations/versions/0024_credential_issuer_wallet_and_configuration.py +32 -0
  32. tba_storage_core/migrations/versions/0025_credential_issuance_and_status.py +34 -0
  33. tba_storage_core/migrations/versions/0026_presentation_transaction_and_replay.py +28 -0
  34. tba_storage_core/migrations/versions/0027_attestation_and_reference_material.py +32 -0
  35. tba_storage_core/migrations/versions/0028_spiffe_svid_and_trust_bundles.py +22 -0
  36. tba_storage_core/migrations/versions/0029_security_event_delivery.py +28 -0
  37. tba_storage_core/migrations/versions/0030_optional_did_and_gnap_state.py +37 -0
  38. tba_storage_core/migrations/versions/0031_certificate_trust_and_status.py +22 -0
  39. tba_storage_core/migrations/versions/0032_claim_definition_and_provenance.py +30 -0
  40. tba_storage_core/migrations/versions/0033_replay_reservations.py +18 -0
  41. tba_storage_core/migrations/versions/0034_dpop_replay_nonce_tables.py +21 -0
  42. tba_storage_core/migrations/versions/0035_webauthn_rp_ceremony_and_credentials.py +76 -0
  43. tba_storage_core/migrations/versions/0036_workload_credentials_artifacts_and_proof_replay.py +27 -0
  44. tba_storage_core/migrations/versions/0037_operator_tables.py +30 -0
  45. tba_storage_core/migrations/versions/0038_adopt_component_ownership.py +66 -0
  46. tba_storage_core/migrations/versions/0039_normalized_platform_iam.py +17 -0
  47. tba_storage_core/py.typed +0 -0
  48. tba_storage_core/storage.lock.toml +211 -0
  49. tba_storage_core/storage.toml +34 -0
  50. tba_storage_core/tables/__init__.py +179 -0
  51. tba_storage_core/tables/_graph_base.py +53 -0
  52. tba_storage_core/tables/_registry.py +200 -0
  53. tba_storage_core/tables/access_review_campaign/__init__.py +9 -0
  54. tba_storage_core/tables/access_review_campaign/_table.py +25 -0
  55. tba_storage_core/tables/access_review_decision/__init__.py +9 -0
  56. tba_storage_core/tables/access_review_decision/_table.py +27 -0
  57. tba_storage_core/tables/access_review_item/__init__.py +9 -0
  58. tba_storage_core/tables/access_review_item/_table.py +26 -0
  59. tba_storage_core/tables/attestation_appraisal_policy/__init__.py +3 -0
  60. tba_storage_core/tables/attestation_endorsement/__init__.py +3 -0
  61. tba_storage_core/tables/attestation_evidence/__init__.py +3 -0
  62. tba_storage_core/tables/attestation_reference_manifest/__init__.py +3 -0
  63. tba_storage_core/tables/attestation_reference_value/__init__.py +3 -0
  64. tba_storage_core/tables/attestation_result/__init__.py +3 -0
  65. tba_storage_core/tables/attestation_state.py +124 -0
  66. tba_storage_core/tables/attribute_policy/__init__.py +9 -0
  67. tba_storage_core/tables/attribute_policy/_table.py +40 -0
  68. tba_storage_core/tables/audit_event/__init__.py +10 -0
  69. tba_storage_core/tables/audit_event/_table.py +47 -0
  70. tba_storage_core/tables/auth_code/__init__.py +13 -0
  71. tba_storage_core/tables/auth_code/_table.py +41 -0
  72. tba_storage_core/tables/auth_session/__init__.py +13 -0
  73. tba_storage_core/tables/auth_session/_table.py +45 -0
  74. tba_storage_core/tables/authentication_challenge/__init__.py +7 -0
  75. tba_storage_core/tables/authentication_challenge/_table.py +38 -0
  76. tba_storage_core/tables/authority_derivation_graph/__init__.py +15 -0
  77. tba_storage_core/tables/authority_derivation_graph/_table.py +94 -0
  78. tba_storage_core/tables/authorization_invariant/__init__.py +5 -0
  79. tba_storage_core/tables/authorization_invariant/_table.py +34 -0
  80. tba_storage_core/tables/authorization_server/__init__.py +6 -0
  81. tba_storage_core/tables/authorization_server/_table.py +61 -0
  82. tba_storage_core/tables/authz_verification_report/__init__.py +9 -0
  83. tba_storage_core/tables/authz_verification_report/_table.py +29 -0
  84. tba_storage_core/tables/backchannel_logout_replay/__init__.py +5 -0
  85. tba_storage_core/tables/backchannel_logout_replay/_table.py +37 -0
  86. tba_storage_core/tables/certificate_record/__init__.py +3 -0
  87. tba_storage_core/tables/certificate_state.py +82 -0
  88. tba_storage_core/tables/certificate_status_snapshot/__init__.py +3 -0
  89. tba_storage_core/tables/claim_state.py +98 -0
  90. tba_storage_core/tables/client/__init__.py +9 -0
  91. tba_storage_core/tables/client/_table.py +41 -0
  92. tba_storage_core/tables/client_registration/__init__.py +10 -0
  93. tba_storage_core/tables/client_registration/_table.py +47 -0
  94. tba_storage_core/tables/consent/__init__.py +13 -0
  95. tba_storage_core/tables/consent/_table.py +57 -0
  96. tba_storage_core/tables/control_correctness_report/__init__.py +9 -0
  97. tba_storage_core/tables/control_correctness_report/_table.py +20 -0
  98. tba_storage_core/tables/credential/__init__.py +9 -0
  99. tba_storage_core/tables/credential/_table.py +74 -0
  100. tba_storage_core/tables/credential_api_key/__init__.py +9 -0
  101. tba_storage_core/tables/credential_api_key/_table.py +50 -0
  102. tba_storage_core/tables/credential_audit_event/__init__.py +9 -0
  103. tba_storage_core/tables/credential_audit_event/_table.py +25 -0
  104. tba_storage_core/tables/credential_client_secret/__init__.py +9 -0
  105. tba_storage_core/tables/credential_client_secret/_table.py +24 -0
  106. tba_storage_core/tables/credential_configuration/__init__.py +3 -0
  107. tba_storage_core/tables/credential_deferred_transaction/__init__.py +3 -0
  108. tba_storage_core/tables/credential_dpop_key/__init__.py +9 -0
  109. tba_storage_core/tables/credential_dpop_key/_table.py +22 -0
  110. tba_storage_core/tables/credential_ecosystem_registry.py +119 -0
  111. tba_storage_core/tables/credential_issuance_state.py +154 -0
  112. tba_storage_core/tables/credential_issuance_transaction/__init__.py +3 -0
  113. tba_storage_core/tables/credential_issuer/__init__.py +3 -0
  114. tba_storage_core/tables/credential_mfa_factor/__init__.py +9 -0
  115. tba_storage_core/tables/credential_mfa_factor/_table.py +24 -0
  116. tba_storage_core/tables/credential_mtls_certificate/__init__.py +9 -0
  117. tba_storage_core/tables/credential_mtls_certificate/_table.py +26 -0
  118. tba_storage_core/tables/credential_notification/__init__.py +3 -0
  119. tba_storage_core/tables/credential_offer/__init__.py +3 -0
  120. tba_storage_core/tables/credential_password/__init__.py +9 -0
  121. tba_storage_core/tables/credential_password/_table.py +24 -0
  122. tba_storage_core/tables/credential_recovery_code/__init__.py +9 -0
  123. tba_storage_core/tables/credential_recovery_code/_table.py +23 -0
  124. tba_storage_core/tables/credential_service_key/__init__.py +9 -0
  125. tba_storage_core/tables/credential_service_key/_table.py +49 -0
  126. tba_storage_core/tables/credential_status_entry/__init__.py +3 -0
  127. tba_storage_core/tables/credential_status_list/__init__.py +3 -0
  128. tba_storage_core/tables/credential_status_publication/__init__.py +3 -0
  129. tba_storage_core/tables/credential_webauthn_passkey/__init__.py +9 -0
  130. tba_storage_core/tables/credential_webauthn_passkey/_table.py +42 -0
  131. tba_storage_core/tables/crypto_key/__init__.py +13 -0
  132. tba_storage_core/tables/crypto_key/_table.py +52 -0
  133. tba_storage_core/tables/crypto_key_version/__init__.py +13 -0
  134. tba_storage_core/tables/crypto_key_version/_table.py +53 -0
  135. tba_storage_core/tables/delegated_admin_scope/__init__.py +9 -0
  136. tba_storage_core/tables/delegated_admin_scope/_table.py +34 -0
  137. tba_storage_core/tables/delegation_grant/__init__.py +13 -0
  138. tba_storage_core/tables/delegation_grant/_table.py +134 -0
  139. tba_storage_core/tables/device_code/__init__.py +13 -0
  140. tba_storage_core/tables/device_code/_table.py +54 -0
  141. tba_storage_core/tables/did_document/__init__.py +3 -0
  142. tba_storage_core/tables/did_document_version/__init__.py +3 -0
  143. tba_storage_core/tables/did_gnap_state.py +169 -0
  144. tba_storage_core/tables/did_resolution_cache/__init__.py +3 -0
  145. tba_storage_core/tables/did_service/__init__.py +3 -0
  146. tba_storage_core/tables/did_verification_method/__init__.py +3 -0
  147. tba_storage_core/tables/dpop_nonce/__init__.py +5 -0
  148. tba_storage_core/tables/dpop_nonce/_table.py +77 -0
  149. tba_storage_core/tables/dpop_replay/__init__.py +5 -0
  150. tba_storage_core/tables/dpop_replay/_table.py +97 -0
  151. tba_storage_core/tables/entitlement/__init__.py +9 -0
  152. tba_storage_core/tables/entitlement/_table.py +22 -0
  153. tba_storage_core/tables/entitlement_assignment/__init__.py +9 -0
  154. tba_storage_core/tables/entitlement_assignment/_table.py +26 -0
  155. tba_storage_core/tables/federated_session/__init__.py +5 -0
  156. tba_storage_core/tables/federated_session/_table.py +32 -0
  157. tba_storage_core/tables/federation/__init__.py +5 -0
  158. tba_storage_core/tables/federation/_table.py +37 -0
  159. tba_storage_core/tables/gnap_client_instance/__init__.py +3 -0
  160. tba_storage_core/tables/gnap_continuation/__init__.py +3 -0
  161. tba_storage_core/tables/gnap_grant/__init__.py +3 -0
  162. tba_storage_core/tables/gnap_interaction/__init__.py +3 -0
  163. tba_storage_core/tables/identity_provider/__init__.py +5 -0
  164. tba_storage_core/tables/identity_provider/_table.py +37 -0
  165. tba_storage_core/tables/invariant_evaluation/__init__.py +5 -0
  166. tba_storage_core/tables/invariant_evaluation/_table.py +29 -0
  167. tba_storage_core/tables/invariant_violation/__init__.py +5 -0
  168. tba_storage_core/tables/invariant_violation/_table.py +27 -0
  169. tba_storage_core/tables/key_attestation_evidence/__init__.py +13 -0
  170. tba_storage_core/tables/key_attestation_evidence/_table.py +99 -0
  171. tba_storage_core/tables/key_envelope/__init__.py +13 -0
  172. tba_storage_core/tables/key_envelope/_table.py +54 -0
  173. tba_storage_core/tables/key_rotation_event/__init__.py +9 -0
  174. tba_storage_core/tables/key_rotation_event/_table.py +44 -0
  175. tba_storage_core/tables/key_rotation_policy/__init__.py +9 -0
  176. tba_storage_core/tables/key_rotation_policy/_table.py +38 -0
  177. tba_storage_core/tables/logout_state/__init__.py +10 -0
  178. tba_storage_core/tables/logout_state/_table.py +67 -0
  179. tba_storage_core/tables/machine_identity/__init__.py +9 -0
  180. tba_storage_core/tables/machine_identity/_table.py +49 -0
  181. tba_storage_core/tables/operator_activity/__init__.py +9 -0
  182. tba_storage_core/tables/operator_activity/_table.py +29 -0
  183. tba_storage_core/tables/operator_audit_event/__init__.py +9 -0
  184. tba_storage_core/tables/operator_audit_event/_table.py +29 -0
  185. tba_storage_core/tables/operator_metadata/__init__.py +9 -0
  186. tba_storage_core/tables/operator_metadata/_table.py +19 -0
  187. tba_storage_core/tables/operator_record/__init__.py +9 -0
  188. tba_storage_core/tables/operator_record/_table.py +44 -0
  189. tba_storage_core/tables/operator_transaction/__init__.py +9 -0
  190. tba_storage_core/tables/operator_transaction/_table.py +33 -0
  191. tba_storage_core/tables/plugin_descriptor/__init__.py +9 -0
  192. tba_storage_core/tables/plugin_descriptor/_table.py +23 -0
  193. tba_storage_core/tables/plugin_lifecycle_event/__init__.py +9 -0
  194. tba_storage_core/tables/plugin_lifecycle_event/_table.py +22 -0
  195. tba_storage_core/tables/policy/__init__.py +5 -0
  196. tba_storage_core/tables/policy/_table.py +30 -0
  197. tba_storage_core/tables/policy_condition/__init__.py +9 -0
  198. tba_storage_core/tables/policy_condition/_table.py +21 -0
  199. tba_storage_core/tables/policy_set/__init__.py +5 -0
  200. tba_storage_core/tables/policy_set/_table.py +33 -0
  201. tba_storage_core/tables/policy_set_member/__init__.py +5 -0
  202. tba_storage_core/tables/policy_set_member/_table.py +32 -0
  203. tba_storage_core/tables/policy_target/__init__.py +5 -0
  204. tba_storage_core/tables/policy_target/_table.py +36 -0
  205. tba_storage_core/tables/policy_version/__init__.py +5 -0
  206. tba_storage_core/tables/policy_version/_table.py +35 -0
  207. tba_storage_core/tables/presentation_consent/__init__.py +3 -0
  208. tba_storage_core/tables/presentation_replay/__init__.py +3 -0
  209. tba_storage_core/tables/presentation_state.py +98 -0
  210. tba_storage_core/tables/presentation_transaction/__init__.py +3 -0
  211. tba_storage_core/tables/principal/__init__.py +9 -0
  212. tba_storage_core/tables/principal/_table.py +49 -0
  213. tba_storage_core/tables/principal_key_binding/__init__.py +13 -0
  214. tba_storage_core/tables/principal_key_binding/_table.py +48 -0
  215. tba_storage_core/tables/provider_artifact/__init__.py +9 -0
  216. tba_storage_core/tables/provider_artifact/_table.py +22 -0
  217. tba_storage_core/tables/pushed_authorization_request/__init__.py +13 -0
  218. tba_storage_core/tables/pushed_authorization_request/_table.py +59 -0
  219. tba_storage_core/tables/realm/__init__.py +13 -0
  220. tba_storage_core/tables/realm/_table.py +87 -0
  221. tba_storage_core/tables/release_attestation_event/__init__.py +9 -0
  222. tba_storage_core/tables/release_attestation_event/_table.py +21 -0
  223. tba_storage_core/tables/release_authorization_state/__init__.py +9 -0
  224. tba_storage_core/tables/release_authorization_state/_table.py +21 -0
  225. tba_storage_core/tables/release_capability_record/__init__.py +9 -0
  226. tba_storage_core/tables/release_capability_record/_table.py +21 -0
  227. tba_storage_core/tables/release_posture/__init__.py +9 -0
  228. tba_storage_core/tables/release_posture/_table.py +21 -0
  229. tba_storage_core/tables/release_security_posture/__init__.py +9 -0
  230. tba_storage_core/tables/release_security_posture/_table.py +22 -0
  231. tba_storage_core/tables/replay_reservation.py +34 -0
  232. tba_storage_core/tables/residency_zone/__init__.py +9 -0
  233. tba_storage_core/tables/residency_zone/_table.py +20 -0
  234. tba_storage_core/tables/resource_server_contract/__init__.py +9 -0
  235. tba_storage_core/tables/resource_server_contract/_table.py +21 -0
  236. tba_storage_core/tables/revoked_token/__init__.py +13 -0
  237. tba_storage_core/tables/revoked_token/_table.py +39 -0
  238. tba_storage_core/tables/role/__init__.py +9 -0
  239. tba_storage_core/tables/role/_table.py +22 -0
  240. tba_storage_core/tables/runtime_qualification/__init__.py +9 -0
  241. tba_storage_core/tables/runtime_qualification/_table.py +21 -0
  242. tba_storage_core/tables/scim_group/__init__.py +9 -0
  243. tba_storage_core/tables/scim_group/_table.py +21 -0
  244. tba_storage_core/tables/scim_patch_event/__init__.py +9 -0
  245. tba_storage_core/tables/scim_patch_event/_table.py +22 -0
  246. tba_storage_core/tables/scim_schema/__init__.py +9 -0
  247. tba_storage_core/tables/scim_schema/_table.py +21 -0
  248. tba_storage_core/tables/scim_user/__init__.py +9 -0
  249. tba_storage_core/tables/scim_user/_table.py +21 -0
  250. tba_storage_core/tables/sdk_package/__init__.py +9 -0
  251. tba_storage_core/tables/sdk_package/_table.py +23 -0
  252. tba_storage_core/tables/security_event/__init__.py +3 -0
  253. tba_storage_core/tables/security_event_delivery/__init__.py +3 -0
  254. tba_storage_core/tables/security_event_replay/__init__.py +3 -0
  255. tba_storage_core/tables/security_event_state.py +100 -0
  256. tba_storage_core/tables/security_event_subscription/__init__.py +3 -0
  257. tba_storage_core/tables/service_identity/__init__.py +9 -0
  258. tba_storage_core/tables/service_identity/_table.py +47 -0
  259. tba_storage_core/tables/spiffe_state.py +83 -0
  260. tba_storage_core/tables/spiffe_trust_bundle/__init__.py +3 -0
  261. tba_storage_core/tables/subject_alias/__init__.py +9 -0
  262. tba_storage_core/tables/subject_alias/_table.py +21 -0
  263. tba_storage_core/tables/svid_record/__init__.py +3 -0
  264. tba_storage_core/tables/tenant/__init__.py +13 -0
  265. tba_storage_core/tables/tenant/_table.py +83 -0
  266. tba_storage_core/tables/tenant_membership/__init__.py +9 -0
  267. tba_storage_core/tables/tenant_membership/_table.py +28 -0
  268. tba_storage_core/tables/tenant_residency/__init__.py +9 -0
  269. tba_storage_core/tables/tenant_residency/_table.py +23 -0
  270. tba_storage_core/tables/token_record/__init__.py +10 -0
  271. tba_storage_core/tables/token_record/_table.py +123 -0
  272. tba_storage_core/tables/trust_anchor/__init__.py +3 -0
  273. tba_storage_core/tables/trust_domain_federation/__init__.py +3 -0
  274. tba_storage_core/tables/trust_federation_graph/__init__.py +15 -0
  275. tba_storage_core/tables/trust_federation_graph/_table.py +94 -0
  276. tba_storage_core/tables/user/__init__.py +13 -0
  277. tba_storage_core/tables/user/_table.py +127 -0
  278. tba_storage_core/tables/verifier_registration/__init__.py +3 -0
  279. tba_storage_core/tables/wallet_attestation/__init__.py +3 -0
  280. tba_storage_core/tables/wallet_instance/__init__.py +3 -0
  281. tba_storage_core/tables/wallet_key_binding/__init__.py +3 -0
  282. tba_storage_core/tables/wallet_registration/__init__.py +3 -0
  283. tba_storage_core/tables/webauthn_attestation_record/__init__.py +3 -0
  284. tba_storage_core/tables/webauthn_attestation_record/_table.py +28 -0
  285. tba_storage_core/tables/webauthn_ceremony/__init__.py +3 -0
  286. tba_storage_core/tables/webauthn_ceremony/_table.py +43 -0
  287. tba_storage_core/tables/webauthn_relying_party/__init__.py +3 -0
  288. tba_storage_core/tables/webauthn_relying_party/_table.py +24 -0
  289. tba_storage_core/tables/workload_credential_state.py +57 -0
  290. tba_storage_core/tables/workload_identity/__init__.py +9 -0
  291. tba_storage_core/tables/workload_identity/_table.py +89 -0
  292. tba_storage_core-0.4.0.dev2.dist-info/METADATA +126 -0
  293. tba_storage_core-0.4.0.dev2.dist-info/RECORD +294 -0
  294. tba_storage_core-0.4.0.dev2.dist-info/WHEEL +4 -0
@@ -0,0 +1,25 @@
1
+ """Storage adapters and Tigrbl/SQLAlchemy tables for the TBA identity package suite."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .migrations.contract import (
6
+ MigrationContract,
7
+ MigrationRevision,
8
+ build_migration_contract,
9
+ collect_migration_revisions,
10
+ )
11
+
12
+
13
+ def ensure_identity_storage_importable() -> None:
14
+ """Compatibility hook for callers that previously used facade path setup."""
15
+
16
+ return None
17
+
18
+
19
+ __all__ = [
20
+ "ensure_identity_storage_importable",
21
+ "MigrationContract",
22
+ "MigrationRevision",
23
+ "build_migration_contract",
24
+ "collect_migration_revisions",
25
+ ]
@@ -0,0 +1,35 @@
1
+ """Explicit full identity-storage composition used by the compatibility facade."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.resources import files
6
+
7
+ try:
8
+ import tomllib
9
+ except ModuleNotFoundError: # pragma: no cover - Python 3.10
10
+ import tomli as tomllib
11
+
12
+ from tigrbl_migrations import (
13
+ StorageComposition,
14
+ discover_components,
15
+ )
16
+ from tigrbl_migrations.version import require_supported_manifest_version
17
+
18
+
19
+ def _load_import_roots() -> tuple[str, ...]:
20
+ profile = tomllib.loads(
21
+ files(__package__).joinpath("storage.toml").read_text(encoding="utf-8")
22
+ )
23
+ require_supported_manifest_version(profile["manifest_version"])
24
+ return tuple(profile["components"])
25
+
26
+
27
+ COMPONENT_IMPORT_ROOTS = _load_import_roots()
28
+
29
+
30
+ def load_full_composition():
31
+ manifests, migrations = discover_components(COMPONENT_IMPORT_ROOTS)
32
+ return StorageComposition.from_manifests(*manifests), migrations
33
+
34
+
35
+ __all__ = ["COMPONENT_IMPORT_ROOTS", "load_full_composition"]
@@ -0,0 +1,3 @@
1
+ """Compatibility projection of the standalone identity storage framework."""
2
+
3
+ from tba_primitives_identity.orm import * # noqa: F401,F403
@@ -0,0 +1,15 @@
1
+ """Repository-owned migration contracts and durable version helpers."""
2
+
3
+ from tba_storage_core.migrations.contract import (
4
+ MigrationContract,
5
+ MigrationRevision,
6
+ build_migration_contract,
7
+ collect_migration_revisions,
8
+ )
9
+
10
+ __all__ = [
11
+ "MigrationContract",
12
+ "MigrationRevision",
13
+ "build_migration_contract",
14
+ "collect_migration_revisions",
15
+ ]
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Iterable
7
+
8
+
9
+ REVISION_PATTERN = re.compile(r"^revision\s*=\s*[\"']([^\"']+)[\"']", re.MULTILINE)
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class MigrationRevision:
14
+ revision: str
15
+ path: str
16
+ ordinal: int
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class MigrationContract:
21
+ revisions: tuple[MigrationRevision, ...]
22
+ required_collections: tuple[str, ...]
23
+
24
+ @property
25
+ def is_ordered(self) -> bool:
26
+ return tuple(item.ordinal for item in self.revisions) == tuple(sorted(item.ordinal for item in self.revisions))
27
+
28
+ @property
29
+ def latest_revision(self) -> str | None:
30
+ return self.revisions[-1].revision if self.revisions else None
31
+
32
+ def assert_complete(self) -> None:
33
+ if not self.revisions:
34
+ raise ValueError("migration contract requires at least one revision")
35
+ if not self.is_ordered:
36
+ raise ValueError("migration revisions must be ordered")
37
+ if len({item.revision for item in self.revisions}) != len(self.revisions):
38
+ raise ValueError("migration revisions must be unique")
39
+
40
+
41
+ def collect_migration_revisions(versions_dir: Path) -> tuple[MigrationRevision, ...]:
42
+ revisions: list[MigrationRevision] = []
43
+ for path in sorted(versions_dir.glob("*.py")):
44
+ if path.name == "__init__.py":
45
+ continue
46
+ text = path.read_text(encoding="utf-8")
47
+ match = REVISION_PATTERN.search(text)
48
+ if match is None:
49
+ continue
50
+ ordinal_text = path.name.split("_", 1)[0]
51
+ ordinal = int(ordinal_text) if ordinal_text.isdigit() else len(revisions) + 1
52
+ revisions.append(MigrationRevision(revision=match.group(1), path=path.as_posix(), ordinal=ordinal))
53
+ return tuple(revisions)
54
+
55
+
56
+ def build_migration_contract(
57
+ *,
58
+ versions_dir: Path,
59
+ required_collections: Iterable[str],
60
+ ) -> MigrationContract:
61
+ contract = MigrationContract(
62
+ revisions=collect_migration_revisions(versions_dir),
63
+ required_collections=tuple(sorted(set(required_collections))),
64
+ )
65
+ contract.assert_complete()
66
+ return contract
67
+
68
+
69
+ __all__ = [
70
+ "MigrationContract",
71
+ "MigrationRevision",
72
+ "build_migration_contract",
73
+ "collect_migration_revisions",
74
+ ]
@@ -0,0 +1,7 @@
1
+ """Migration environment metadata exports."""
2
+
3
+ from tba_storage_core.tables import RestOltpTable
4
+
5
+ TARGET_METADATA = RestOltpTable.metadata
6
+
7
+ __all__ = ["TARGET_METADATA"]
@@ -0,0 +1,219 @@
1
+ """DDL helpers for executable repository-owned migrations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterable
6
+ from typing import Protocol
7
+
8
+ from sqlalchemy import Column, ForeignKey, MetaData, Table, inspect, text
9
+ from sqlalchemy.exc import NoSuchTableError, SQLAlchemyError
10
+
11
+
12
+ class SupportsTable(Protocol):
13
+ __table__: object
14
+
15
+
16
+ AUTHN_SCHEMA = "authn"
17
+ MIGRATION_TABLE = "schema_migrations"
18
+
19
+
20
+ def ensure_schema(conn) -> None:
21
+ if conn.dialect.name != "sqlite":
22
+ conn.exec_driver_sql(f"CREATE SCHEMA IF NOT EXISTS {AUTHN_SCHEMA}")
23
+
24
+
25
+ def ensure_migration_table(conn) -> None:
26
+ ensure_schema(conn)
27
+ if conn.dialect.name == "sqlite":
28
+ conn.exec_driver_sql(
29
+ f"CREATE TABLE IF NOT EXISTS {MIGRATION_TABLE} (revision VARCHAR(128) PRIMARY KEY, applied_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP)"
30
+ )
31
+ return
32
+ conn.exec_driver_sql(
33
+ f"CREATE TABLE IF NOT EXISTS {AUTHN_SCHEMA}.{MIGRATION_TABLE} (revision VARCHAR(128) PRIMARY KEY, applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW())"
34
+ )
35
+
36
+
37
+ def applied_revisions(conn) -> set[str]:
38
+ ensure_migration_table(conn)
39
+ if conn.dialect.name == "sqlite":
40
+ rows = conn.exec_driver_sql(f"SELECT revision FROM {MIGRATION_TABLE}").fetchall()
41
+ else:
42
+ rows = conn.exec_driver_sql(f"SELECT revision FROM {AUTHN_SCHEMA}.{MIGRATION_TABLE}").fetchall()
43
+ return {str(row[0]) for row in rows}
44
+
45
+
46
+ def mark_revision(conn, revision: str) -> None:
47
+ ensure_migration_table(conn)
48
+ table = MIGRATION_TABLE if conn.dialect.name == "sqlite" else f"{AUTHN_SCHEMA}.{MIGRATION_TABLE}"
49
+ conn.execute(text(f"INSERT INTO {table} (revision) VALUES (:revision)"), {"revision": revision})
50
+
51
+
52
+ def unmark_revision(conn, revision: str) -> None:
53
+ ensure_migration_table(conn)
54
+ table = MIGRATION_TABLE if conn.dialect.name == "sqlite" else f"{AUTHN_SCHEMA}.{MIGRATION_TABLE}"
55
+ conn.execute(text(f"DELETE FROM {table} WHERE revision = :revision"), {"revision": revision})
56
+
57
+
58
+ def _ddl_tables(conn, *models: SupportsTable) -> list[Table]:
59
+ return [model.__table__ for model in models]
60
+
61
+
62
+ def create_tables(conn, *models: SupportsTable) -> None:
63
+ ensure_schema(conn)
64
+ for table in _ddl_tables(conn, *models):
65
+ table.create(bind=conn, checkfirst=True)
66
+
67
+
68
+ def drop_tables(conn, *models: SupportsTable) -> None:
69
+ for table in reversed(_ddl_tables(conn, *models)):
70
+ table.drop(bind=conn, checkfirst=True)
71
+
72
+
73
+ def table_names(conn) -> set[str]:
74
+ inspector = inspect(conn)
75
+ if conn.dialect.name == "sqlite":
76
+ return set(inspector.get_table_names(schema=AUTHN_SCHEMA))
77
+ return set(inspector.get_table_names(schema=AUTHN_SCHEMA))
78
+
79
+
80
+ def column_names(conn, table: str) -> set[str]:
81
+ inspector = inspect(conn)
82
+ try:
83
+ cols = inspector.get_columns(
84
+ table,
85
+ schema=AUTHN_SCHEMA if conn.dialect.name == "sqlite" else AUTHN_SCHEMA,
86
+ )
87
+ except NoSuchTableError:
88
+ return set()
89
+ return {str(col["name"]) for col in cols}
90
+
91
+
92
+ def _table_name(conn, table: str) -> str:
93
+ return f'"{AUTHN_SCHEMA}"."{table}"'
94
+
95
+
96
+ def _index_name(conn, index: str) -> str:
97
+ if conn.dialect.name == "sqlite":
98
+ return f'"{AUTHN_SCHEMA}"."{index}"'
99
+ return f'"{index}"'
100
+
101
+
102
+ def _copy_sqlalchemy_column(column, *, preserve_foreign_keys: bool = True) -> Column:
103
+ args = [column.type]
104
+ if preserve_foreign_keys:
105
+ for fk in column.foreign_keys:
106
+ target = fk.target_fullname
107
+ if target.startswith(f"{AUTHN_SCHEMA}."):
108
+ target = target[len(AUTHN_SCHEMA) + 1 :]
109
+ args.append(
110
+ ForeignKey(
111
+ target,
112
+ onupdate=fk.onupdate,
113
+ ondelete=fk.ondelete,
114
+ deferrable=fk.deferrable,
115
+ initially=fk.initially,
116
+ name=fk.constraint.name if fk.constraint is not None else None,
117
+ )
118
+ )
119
+ server_default = None
120
+ if column.server_default is not None and getattr(column.server_default, "arg", None) is not None:
121
+ server_default = column.server_default.arg
122
+ return Column(
123
+ column.name,
124
+ *args,
125
+ primary_key=column.primary_key,
126
+ nullable=column.nullable,
127
+ unique=bool(column.unique),
128
+ autoincrement=column.autoincrement,
129
+ server_default=server_default,
130
+ comment=column.comment,
131
+ )
132
+
133
+
134
+ def _sqlite_rebuild_without_columns(conn, table: str, columns_to_drop: Iterable[str]) -> None:
135
+ drop_set = {name for name in columns_to_drop if name in column_names(conn, table)}
136
+ if not drop_set:
137
+ return
138
+
139
+ old_meta = MetaData()
140
+ old_table = Table(table, old_meta, schema=AUTHN_SCHEMA, autoload_with=conn)
141
+ keep_columns = [column for column in old_table.columns if column.name not in drop_set]
142
+ if not keep_columns:
143
+ raise ValueError(f"cannot rebuild SQLite table '{table}' without any remaining columns")
144
+
145
+ old_indexes = inspect(conn).get_indexes(table, schema=AUTHN_SCHEMA)
146
+ tmp_table_name = f"__tmp__{table}"
147
+
148
+ conn.exec_driver_sql("PRAGMA foreign_keys=OFF")
149
+ try:
150
+ new_meta = MetaData()
151
+ new_table = Table(
152
+ tmp_table_name,
153
+ new_meta,
154
+ schema=AUTHN_SCHEMA,
155
+ *(_copy_sqlalchemy_column(column, preserve_foreign_keys=False) for column in keep_columns),
156
+ )
157
+ new_table.create(bind=conn, checkfirst=False)
158
+
159
+ keep_names = [column.name for column in keep_columns]
160
+ select_columns = ", ".join(f'"{name}"' for name in keep_names)
161
+ conn.exec_driver_sql(
162
+ f'INSERT INTO {_table_name(conn, tmp_table_name)} ({select_columns}) '
163
+ f'SELECT {select_columns} FROM {_table_name(conn, table)}'
164
+ )
165
+ conn.exec_driver_sql(f'DROP TABLE {_table_name(conn, table)}')
166
+ conn.exec_driver_sql(
167
+ f'ALTER TABLE {_table_name(conn, tmp_table_name)} RENAME TO "{table}"'
168
+ )
169
+
170
+ for index in old_indexes:
171
+ index_columns = list(index.get("column_names") or [])
172
+ if not index_columns or any(name in drop_set for name in index_columns):
173
+ continue
174
+ unique_sql = "UNIQUE " if bool(index.get("unique", False)) else ""
175
+ rendered = ", ".join(f'"{name}"' for name in index_columns)
176
+ conn.exec_driver_sql(
177
+ f"CREATE {unique_sql}INDEX IF NOT EXISTS {_index_name(conn, index['name'])} "
178
+ f'ON "{table}" ({rendered})'
179
+ )
180
+ finally:
181
+ conn.exec_driver_sql("PRAGMA foreign_keys=ON")
182
+
183
+
184
+ def drop_columns(conn, table: str, columns_to_drop: Iterable[str]) -> None:
185
+ drop_list = [name for name in columns_to_drop if name in column_names(conn, table)]
186
+ if not drop_list:
187
+ return
188
+
189
+ if conn.dialect.name == "sqlite":
190
+ remaining = list(drop_list)
191
+ for name in drop_list:
192
+ try:
193
+ conn.exec_driver_sql(f'ALTER TABLE {_table_name(conn, table)} DROP COLUMN "{name}"')
194
+ remaining.remove(name)
195
+ except SQLAlchemyError:
196
+ continue
197
+ if remaining:
198
+ _sqlite_rebuild_without_columns(conn, table, remaining)
199
+ return
200
+
201
+ rendered_table = _table_name(conn, table)
202
+ for name in drop_list:
203
+ conn.exec_driver_sql(f'ALTER TABLE {rendered_table} DROP COLUMN IF EXISTS "{name}"')
204
+
205
+
206
+ __all__ = [
207
+ "AUTHN_SCHEMA",
208
+ "MIGRATION_TABLE",
209
+ "applied_revisions",
210
+ "column_names",
211
+ "create_tables",
212
+ "drop_columns",
213
+ "drop_tables",
214
+ "ensure_migration_table",
215
+ "ensure_schema",
216
+ "mark_revision",
217
+ "table_names",
218
+ "unmark_revision",
219
+ ]
@@ -0,0 +1,17 @@
1
+ """Executable DDL migration for 0001_initial_identity_tables."""
2
+
3
+ from tba_storage_core.migrations.helpers import create_tables, drop_tables
4
+ from tba_storage_core.tables import Realm, Tenant, User
5
+
6
+ revision = '0001_initial_identity_tables'
7
+ down_revision = None
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade(conn) -> None:
13
+ create_tables(conn, Realm, Tenant, User)
14
+
15
+
16
+ def downgrade(conn) -> None:
17
+ drop_tables(conn, Realm, Tenant, User)
@@ -0,0 +1,23 @@
1
+ """Executable DDL migration for 0002_client_and_service_tables."""
2
+
3
+ from tba_storage_core.migrations.helpers import create_tables, drop_tables
4
+ from tba_storage_core.tables import (
5
+ Client,
6
+ ClientRegistration,
7
+ CredentialApiKey,
8
+ CredentialServiceKey,
9
+ ServiceIdentity,
10
+ )
11
+
12
+ revision = '0002_client_and_service_tables'
13
+ down_revision = '0001_initial_identity_tables'
14
+ branch_labels = None
15
+ depends_on = None
16
+
17
+
18
+ def upgrade(conn) -> None:
19
+ create_tables(conn, Client, ClientRegistration, ServiceIdentity, CredentialServiceKey, CredentialApiKey)
20
+
21
+
22
+ def downgrade(conn) -> None:
23
+ drop_tables(conn, Client, ClientRegistration, ServiceIdentity, CredentialServiceKey, CredentialApiKey)
@@ -0,0 +1,17 @@
1
+ """Executable DDL migration for 0003_authorization_runtime_tables."""
2
+
3
+ from tba_storage_core.migrations.helpers import create_tables, drop_tables
4
+ from tba_storage_core.tables import AuthSession, AuthCode, Consent, TokenRecord
5
+
6
+ revision = '0003_authorization_runtime_tables'
7
+ down_revision = '0002_client_and_service_tables'
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade(conn) -> None:
13
+ create_tables(conn, AuthSession, AuthCode, Consent, TokenRecord)
14
+
15
+
16
+ def downgrade(conn) -> None:
17
+ drop_tables(conn, AuthSession, AuthCode, Consent, TokenRecord)
@@ -0,0 +1,17 @@
1
+ """Executable DDL migration for 0004_device_par_revocation_tables."""
2
+
3
+ from tba_storage_core.migrations.helpers import create_tables, drop_tables
4
+ from tba_storage_core.tables import DeviceCode, PushedAuthorizationRequest, RevokedToken
5
+
6
+ revision = '0004_device_par_revocation_tables'
7
+ down_revision = '0003_authorization_runtime_tables'
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade(conn) -> None:
13
+ create_tables(conn, DeviceCode, PushedAuthorizationRequest, RevokedToken)
14
+
15
+
16
+ def downgrade(conn) -> None:
17
+ drop_tables(conn, DeviceCode, PushedAuthorizationRequest, RevokedToken)
@@ -0,0 +1,17 @@
1
+ """Executable DDL migration for 0005_session_logout_tables."""
2
+
3
+ from tba_storage_core.migrations.helpers import create_tables, drop_tables
4
+ from tba_storage_core.tables import LogoutState
5
+
6
+ revision = '0005_session_logout_tables'
7
+ down_revision = '0004_device_par_revocation_tables'
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade(conn) -> None:
13
+ create_tables(conn, LogoutState)
14
+
15
+
16
+ def downgrade(conn) -> None:
17
+ drop_tables(conn, LogoutState)
@@ -0,0 +1,17 @@
1
+ """Executable DDL migration for 0006_key_rotation_and_audit_tables."""
2
+
3
+ from tba_storage_core.migrations.helpers import create_tables, drop_tables
4
+ from tba_storage_core.tables import KeyRotationEvent, AuditEvent
5
+
6
+ revision = '0006_key_rotation_and_audit_tables'
7
+ down_revision = '0005_session_logout_tables'
8
+ branch_labels = None
9
+ depends_on = None
10
+
11
+
12
+ def upgrade(conn) -> None:
13
+ create_tables(conn, KeyRotationEvent, AuditEvent)
14
+
15
+
16
+ def downgrade(conn) -> None:
17
+ drop_tables(conn, KeyRotationEvent, AuditEvent)
@@ -0,0 +1,50 @@
1
+ """Executable DDL migration for 0007_browser_session_cookie_and_auth_code_linkage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tba_storage_core.migrations.helpers import column_names, drop_columns, AUTHN_SCHEMA
6
+
7
+ revision = '0007_browser_session_cookie_and_auth_code_linkage'
8
+ down_revision = '0006_key_rotation_and_audit_tables'
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+ SESSION_COLUMNS = (
13
+ 'session_state_salt',
14
+ 'cookie_secret_hash',
15
+ 'cookie_issued_at',
16
+ 'cookie_rotated_at',
17
+ )
18
+ AUTH_CODE_COLUMNS = ('session_id',)
19
+
20
+
21
+ def _table_name(conn, name: str) -> str:
22
+ return f"{AUTHN_SCHEMA}.{name}"
23
+
24
+
25
+ def _add_column(conn, table: str, ddl: str) -> None:
26
+ conn.exec_driver_sql(f"ALTER TABLE {_table_name(conn, table)} ADD COLUMN {ddl}")
27
+
28
+
29
+ def upgrade(conn) -> None:
30
+ session_cols = column_names(conn, 'sessions')
31
+ if 'session_state_salt' not in session_cols:
32
+ _add_column(conn, 'sessions', 'session_state_salt VARCHAR(128)')
33
+ if 'cookie_secret_hash' not in session_cols:
34
+ _add_column(conn, 'sessions', 'cookie_secret_hash VARCHAR(128)')
35
+ if 'cookie_issued_at' not in session_cols:
36
+ _add_column(conn, 'sessions', 'cookie_issued_at TIMESTAMP')
37
+ if 'cookie_rotated_at' not in session_cols:
38
+ _add_column(conn, 'sessions', 'cookie_rotated_at TIMESTAMP')
39
+
40
+ auth_code_cols = column_names(conn, 'auth_codes')
41
+ if 'session_id' not in auth_code_cols:
42
+ if conn.dialect.name == 'sqlite':
43
+ _add_column(conn, 'auth_codes', 'session_id CHAR(32)')
44
+ else:
45
+ _add_column(conn, 'auth_codes', 'session_id UUID')
46
+
47
+
48
+ def downgrade(conn) -> None:
49
+ drop_columns(conn, 'auth_codes', AUTH_CODE_COLUMNS)
50
+ drop_columns(conn, 'sessions', SESSION_COLUMNS)
@@ -0,0 +1,44 @@
1
+ """Executable DDL migration for 0008_refresh_token_family_state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tba_storage_core.migrations.helpers import AUTHN_SCHEMA, column_names, drop_columns
6
+
7
+ revision = "0008_refresh_token_family_state"
8
+ down_revision = "0007_browser_session_cookie_and_auth_code_linkage"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+ TOKEN_RECORD_COLUMNS = (
13
+ "refresh_family_id",
14
+ "refresh_parent_hash",
15
+ "refresh_successor_hash",
16
+ "used_at",
17
+ "reuse_detected_at",
18
+ )
19
+
20
+
21
+ def _table_name(conn, name: str) -> str:
22
+ return f"{AUTHN_SCHEMA}.{name}"
23
+
24
+
25
+ def _add_column(conn, table: str, ddl: str) -> None:
26
+ conn.exec_driver_sql(f"ALTER TABLE {_table_name(conn, table)} ADD COLUMN {ddl}")
27
+
28
+
29
+ def upgrade(conn) -> None:
30
+ token_cols = column_names(conn, "token_records")
31
+ if "refresh_family_id" not in token_cols:
32
+ _add_column(conn, "token_records", "refresh_family_id VARCHAR(64)")
33
+ if "refresh_parent_hash" not in token_cols:
34
+ _add_column(conn, "token_records", "refresh_parent_hash VARCHAR(128)")
35
+ if "refresh_successor_hash" not in token_cols:
36
+ _add_column(conn, "token_records", "refresh_successor_hash VARCHAR(128)")
37
+ if "used_at" not in token_cols:
38
+ _add_column(conn, "token_records", "used_at TIMESTAMP")
39
+ if "reuse_detected_at" not in token_cols:
40
+ _add_column(conn, "token_records", "reuse_detected_at TIMESTAMP")
41
+
42
+
43
+ def downgrade(conn) -> None:
44
+ drop_columns(conn, "token_records", TOKEN_RECORD_COLUMNS)
@@ -0,0 +1,48 @@
1
+ """Executable DDL migration for 0009_admin_identity_bootstrap_and_password_recovery."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tba_storage_core.migrations.helpers import AUTHN_SCHEMA, column_names, drop_columns
6
+
7
+ revision = "0009_admin_identity_bootstrap_and_password_recovery"
8
+ down_revision = "0008_refresh_token_family_state"
9
+ branch_labels = None
10
+ depends_on = None
11
+
12
+ USER_COLUMNS = (
13
+ "is_admin",
14
+ "is_superuser",
15
+ "must_change_password",
16
+ "password_reset_token_hash",
17
+ "password_reset_expires_at",
18
+ )
19
+
20
+
21
+ def _table_name(name: str) -> str:
22
+ return f"{AUTHN_SCHEMA}.{name}"
23
+
24
+
25
+ def _add_column(conn, table: str, ddl: str) -> None:
26
+ conn.exec_driver_sql(f"ALTER TABLE {_table_name(table)} ADD COLUMN {ddl}")
27
+
28
+
29
+ def _bool_default(conn) -> str:
30
+ return "0" if conn.dialect.name == "sqlite" else "FALSE"
31
+
32
+
33
+ def upgrade(conn) -> None:
34
+ user_cols = column_names(conn, "users")
35
+ if "is_admin" not in user_cols:
36
+ _add_column(conn, "users", f"is_admin BOOLEAN NOT NULL DEFAULT {_bool_default(conn)}")
37
+ if "is_superuser" not in user_cols:
38
+ _add_column(conn, "users", f"is_superuser BOOLEAN NOT NULL DEFAULT {_bool_default(conn)}")
39
+ if "must_change_password" not in user_cols:
40
+ _add_column(conn, "users", f"must_change_password BOOLEAN NOT NULL DEFAULT {_bool_default(conn)}")
41
+ if "password_reset_token_hash" not in user_cols:
42
+ _add_column(conn, "users", "password_reset_token_hash VARCHAR(128)")
43
+ if "password_reset_expires_at" not in user_cols:
44
+ _add_column(conn, "users", "password_reset_expires_at TIMESTAMP")
45
+
46
+
47
+ def downgrade(conn) -> None:
48
+ drop_columns(conn, "users", USER_COLUMNS)
@@ -0,0 +1,33 @@
1
+ """Executable DDL migration for 0010_realm_namespace_tables."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tba_storage_core.migrations.helpers import AUTHN_SCHEMA, column_names, create_tables, drop_columns, table_names
6
+ from tba_storage_core.tables import Realm
7
+
8
+ revision = "0010_realm_namespace_tables"
9
+ down_revision = "0009_admin_identity_bootstrap_and_password_recovery"
10
+ branch_labels = None
11
+ depends_on = None
12
+
13
+ DEFAULT_REALM_ID = "FFFFFFFF-1000-0000-0000-000000000000"
14
+
15
+
16
+ def _table_name(table: str) -> str:
17
+ return f'"{AUTHN_SCHEMA}"."{table}"'
18
+
19
+
20
+ def _add_column(conn, table: str, ddl: str) -> None:
21
+ conn.exec_driver_sql(f"ALTER TABLE {_table_name(table)} ADD COLUMN {ddl}")
22
+
23
+
24
+ def upgrade(conn) -> None:
25
+ create_tables(conn, Realm)
26
+ if "tenants" in table_names(conn) and "realm_id" not in column_names(conn, "tenants"):
27
+ _add_column(conn, "tenants", "realm_id UUID")
28
+
29
+
30
+ def downgrade(conn) -> None:
31
+ if "tenants" in table_names(conn):
32
+ drop_columns(conn, "tenants", ("realm_id",))
33
+ Realm.__table__.drop(bind=conn, checkfirst=True)