identity-hub 1.0.0a1__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 (346) hide show
  1. identity_hub-1.0.0a1/.gitignore +35 -0
  2. identity_hub-1.0.0a1/CHANGELOG.md +521 -0
  3. identity_hub-1.0.0a1/LICENSE +21 -0
  4. identity_hub-1.0.0a1/PKG-INFO +254 -0
  5. identity_hub-1.0.0a1/README.md +213 -0
  6. identity_hub-1.0.0a1/docs/cli.md +344 -0
  7. identity_hub-1.0.0a1/docs/guide/01-concepts.md +251 -0
  8. identity_hub-1.0.0a1/docs/guide/02-services.md +180 -0
  9. identity_hub-1.0.0a1/docs/guide/03-accounts.md +478 -0
  10. identity_hub-1.0.0a1/docs/guide/04-states.md +345 -0
  11. identity_hub-1.0.0a1/docs/guide/05-login.md +180 -0
  12. identity_hub-1.0.0a1/docs/guide/06-verification.md +161 -0
  13. identity_hub-1.0.0a1/docs/guide/07-leases.md +254 -0
  14. identity_hub-1.0.0a1/docs/guide/08-limits.md +218 -0
  15. identity_hub-1.0.0a1/docs/guide/09-proxies.md +150 -0
  16. identity_hub-1.0.0a1/docs/guide/10-events.md +237 -0
  17. identity_hub-1.0.0a1/docs/guide/11-testing.md +159 -0
  18. identity_hub-1.0.0a1/docs/guide/12-recipes.md +158 -0
  19. identity_hub-1.0.0a1/docs/guide/README.md +31 -0
  20. identity_hub-1.0.0a1/docs/guide/api.md +259 -0
  21. identity_hub-1.0.0a1/docs/guide/limitations.md +44 -0
  22. identity_hub-1.0.0a1/docs/integrations.md +353 -0
  23. identity_hub-1.0.0a1/docs/perf.md +48 -0
  24. identity_hub-1.0.0a1/docs/traceability.md +176 -0
  25. identity_hub-1.0.0a1/examples/__init__.py +1 -0
  26. identity_hub-1.0.0a1/examples/account_lifecycle.py +228 -0
  27. identity_hub-1.0.0a1/examples/telegram_service.py +142 -0
  28. identity_hub-1.0.0a1/examples/test_memory_hub.py +22 -0
  29. identity_hub-1.0.0a1/pyproject.toml +166 -0
  30. identity_hub-1.0.0a1/src/identity_hub/__init__.py +239 -0
  31. identity_hub-1.0.0a1/src/identity_hub/_ids.py +44 -0
  32. identity_hub-1.0.0a1/src/identity_hub/_space.py +160 -0
  33. identity_hub-1.0.0a1/src/identity_hub/admin_audit.py +123 -0
  34. identity_hub-1.0.0a1/src/identity_hub/alembic.py +30 -0
  35. identity_hub-1.0.0a1/src/identity_hub/api/__init__.py +9 -0
  36. identity_hub-1.0.0a1/src/identity_hub/api/_base.py +27 -0
  37. identity_hub-1.0.0a1/src/identity_hub/api/admin.py +247 -0
  38. identity_hub-1.0.0a1/src/identity_hub/api/analytics.py +82 -0
  39. identity_hub-1.0.0a1/src/identity_hub/api/auth.py +46 -0
  40. identity_hub-1.0.0a1/src/identity_hub/api/data.py +48 -0
  41. identity_hub-1.0.0a1/src/identity_hub/api/identities.py +603 -0
  42. identity_hub-1.0.0a1/src/identity_hub/api/imports.py +131 -0
  43. identity_hub-1.0.0a1/src/identity_hub/api/links.py +60 -0
  44. identity_hub-1.0.0a1/src/identity_hub/api/maintenance.py +33 -0
  45. identity_hub-1.0.0a1/src/identity_hub/api/pools.py +64 -0
  46. identity_hub-1.0.0a1/src/identity_hub/api/proxies.py +267 -0
  47. identity_hub-1.0.0a1/src/identity_hub/api/secrets.py +216 -0
  48. identity_hub-1.0.0a1/src/identity_hub/api/sync.py +58 -0
  49. identity_hub-1.0.0a1/src/identity_hub/api/verifications.py +83 -0
  50. identity_hub-1.0.0a1/src/identity_hub/auth.py +626 -0
  51. identity_hub-1.0.0a1/src/identity_hub/cli.py +379 -0
  52. identity_hub-1.0.0a1/src/identity_hub/clock.py +139 -0
  53. identity_hub-1.0.0a1/src/identity_hub/context.py +35 -0
  54. identity_hub-1.0.0a1/src/identity_hub/crypto/__init__.py +33 -0
  55. identity_hub-1.0.0a1/src/identity_hub/crypto/envelope.py +250 -0
  56. identity_hub-1.0.0a1/src/identity_hub/crypto/fingerprint.py +95 -0
  57. identity_hub-1.0.0a1/src/identity_hub/crypto/providers.py +218 -0
  58. identity_hub-1.0.0a1/src/identity_hub/crypto/secret_value.py +65 -0
  59. identity_hub-1.0.0a1/src/identity_hub/dispatch/__init__.py +37 -0
  60. identity_hub-1.0.0a1/src/identity_hub/dispatch/dispatcher.py +259 -0
  61. identity_hub-1.0.0a1/src/identity_hub/dispatch/notifications.py +552 -0
  62. identity_hub-1.0.0a1/src/identity_hub/dispatch/outbound.py +249 -0
  63. identity_hub-1.0.0a1/src/identity_hub/dispatch/webhooks.py +580 -0
  64. identity_hub-1.0.0a1/src/identity_hub/doctor.py +214 -0
  65. identity_hub-1.0.0a1/src/identity_hub/domain/__init__.py +1 -0
  66. identity_hub-1.0.0a1/src/identity_hub/domain/admissibility.py +118 -0
  67. identity_hub-1.0.0a1/src/identity_hub/domain/credentials.py +82 -0
  68. identity_hub-1.0.0a1/src/identity_hub/domain/data.py +154 -0
  69. identity_hub-1.0.0a1/src/identity_hub/domain/idempotency.py +162 -0
  70. identity_hub-1.0.0a1/src/identity_hub/domain/provider_operations.py +107 -0
  71. identity_hub-1.0.0a1/src/identity_hub/domain/query.py +159 -0
  72. identity_hub-1.0.0a1/src/identity_hub/domain/redaction.py +110 -0
  73. identity_hub-1.0.0a1/src/identity_hub/domain/region_specs.py +139 -0
  74. identity_hub-1.0.0a1/src/identity_hub/domain/regions.py +496 -0
  75. identity_hub-1.0.0a1/src/identity_hub/domain/verification.py +315 -0
  76. identity_hub-1.0.0a1/src/identity_hub/enums.py +117 -0
  77. identity_hub-1.0.0a1/src/identity_hub/errors.py +431 -0
  78. identity_hub-1.0.0a1/src/identity_hub/events/__init__.py +27 -0
  79. identity_hub-1.0.0a1/src/identity_hub/events/catalog.py +284 -0
  80. identity_hub-1.0.0a1/src/identity_hub/heartbeat.py +68 -0
  81. identity_hub-1.0.0a1/src/identity_hub/hub.py +527 -0
  82. identity_hub-1.0.0a1/src/identity_hub/identities.py +560 -0
  83. identity_hub-1.0.0a1/src/identity_hub/identity_data.py +84 -0
  84. identity_hub-1.0.0a1/src/identity_hub/importing.py +897 -0
  85. identity_hub-1.0.0a1/src/identity_hub/lease.py +928 -0
  86. identity_hub-1.0.0a1/src/identity_hub/links.py +143 -0
  87. identity_hub-1.0.0a1/src/identity_hub/logging.py +177 -0
  88. identity_hub-1.0.0a1/src/identity_hub/maintenance.py +220 -0
  89. identity_hub-1.0.0a1/src/identity_hub/metrics.py +265 -0
  90. identity_hub-1.0.0a1/src/identity_hub/operation.py +330 -0
  91. identity_hub-1.0.0a1/src/identity_hub/pool.py +311 -0
  92. identity_hub-1.0.0a1/src/identity_hub/postgres/__init__.py +22 -0
  93. identity_hub-1.0.0a1/src/identity_hub/postgres/migrate.py +138 -0
  94. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/__init__.py +0 -0
  95. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/env.py +78 -0
  96. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/script.py.mako +30 -0
  97. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0001_initial_schema_v1.py +965 -0
  98. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0002_encrypted_profile.py +46 -0
  99. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0003_proxy_pending_observation.py +33 -0
  100. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0004_proxy_bound_count.py +35 -0
  101. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0005_admin_data_foundations.py +244 -0
  102. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0006_stats_rollups.py +91 -0
  103. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0007_state_snapshots.py +82 -0
  104. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0008_normalized_ref.py +87 -0
  105. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0009_idempotency_keys.py +118 -0
  106. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0010_lease_idempotency_scope.py +63 -0
  107. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0011_open_lease_history_index.py +43 -0
  108. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0012_notification_dedup_and_channel_pause.py +57 -0
  109. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0013_probe_attempted_at.py +39 -0
  110. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0014_integrity_and_background_indexes.py +163 -0
  111. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0015_retention_indexes.py +80 -0
  112. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0016_recount_proxy_bindings.py +37 -0
  113. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0017_identity_revision.py +50 -0
  114. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0018_proxy_fingerprint_key.py +38 -0
  115. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0019_operation_key_fingerprint_key.py +36 -0
  116. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0020_last_leased_at.py +73 -0
  117. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0021_hot_heartbeat.py +54 -0
  118. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0022_multiple_credentials.py +84 -0
  119. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0023_session_history.py +89 -0
  120. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/0024_provider_operation_revision.py +59 -0
  121. identity_hub-1.0.0a1/src/identity_hub/postgres/migrations/versions/__init__.py +0 -0
  122. identity_hub-1.0.0a1/src/identity_hub/postgres/models.py +1309 -0
  123. identity_hub-1.0.0a1/src/identity_hub/postgres/read_schema.py +195 -0
  124. identity_hub-1.0.0a1/src/identity_hub/postgres/store.py +6063 -0
  125. identity_hub-1.0.0a1/src/identity_hub/protocols.py +281 -0
  126. identity_hub-1.0.0a1/src/identity_hub/proxies.py +657 -0
  127. identity_hub-1.0.0a1/src/identity_hub/py.typed +0 -0
  128. identity_hub-1.0.0a1/src/identity_hub/quotas.py +114 -0
  129. identity_hub-1.0.0a1/src/identity_hub/read/__init__.py +77 -0
  130. identity_hub-1.0.0a1/src/identity_hub/read/admin_state.py +157 -0
  131. identity_hub-1.0.0a1/src/identity_hub/read/analytics.py +594 -0
  132. identity_hub-1.0.0a1/src/identity_hub/read/background.py +152 -0
  133. identity_hub-1.0.0a1/src/identity_hub/read/cards.py +404 -0
  134. identity_hub-1.0.0a1/src/identity_hub/read/deliveries.py +316 -0
  135. identity_hub-1.0.0a1/src/identity_hub/read/events.py +171 -0
  136. identity_hub-1.0.0a1/src/identity_hub/read/human_queue.py +291 -0
  137. identity_hub-1.0.0a1/src/identity_hub/read/leases.py +308 -0
  138. identity_hub-1.0.0a1/src/identity_hub/read/overview.py +263 -0
  139. identity_hub-1.0.0a1/src/identity_hub/read/proxies.py +201 -0
  140. identity_hub-1.0.0a1/src/identity_hub/read/search.py +575 -0
  141. identity_hub-1.0.0a1/src/identity_hub/read/services.py +246 -0
  142. identity_hub-1.0.0a1/src/identity_hub/rollups.py +408 -0
  143. identity_hub-1.0.0a1/src/identity_hub/secrets_admin.py +348 -0
  144. identity_hub-1.0.0a1/src/identity_hub/service.py +409 -0
  145. identity_hub-1.0.0a1/src/identity_hub/snapshots.py +72 -0
  146. identity_hub-1.0.0a1/src/identity_hub/state.py +296 -0
  147. identity_hub-1.0.0a1/src/identity_hub/store/__init__.py +5 -0
  148. identity_hub-1.0.0a1/src/identity_hub/store/base.py +2092 -0
  149. identity_hub-1.0.0a1/src/identity_hub/sync.py +243 -0
  150. identity_hub-1.0.0a1/src/identity_hub/telegram_notify/__init__.py +104 -0
  151. identity_hub-1.0.0a1/src/identity_hub/testing/__init__.py +12 -0
  152. identity_hub-1.0.0a1/src/identity_hub/testing/conformance.py +401 -0
  153. identity_hub-1.0.0a1/src/identity_hub/testing/factories.py +117 -0
  154. identity_hub-1.0.0a1/src/identity_hub/testing/memory.py +3956 -0
  155. identity_hub-1.0.0a1/src/identity_hub/testing/pytest_plugin.py +116 -0
  156. identity_hub-1.0.0a1/src/identity_hub/verifications.py +378 -0
  157. identity_hub-1.0.0a1/tests/__init__.py +0 -0
  158. identity_hub-1.0.0a1/tests/conformance/conftest.py +37 -0
  159. identity_hub-1.0.0a1/tests/conformance/test_account_search.py +165 -0
  160. identity_hub-1.0.0a1/tests/conformance/test_admin_analytics_maintenance_api.py +55 -0
  161. identity_hub-1.0.0a1/tests/conformance/test_auth_sync_links_api.py +227 -0
  162. identity_hub-1.0.0a1/tests/conformance/test_credentials_flow.py +143 -0
  163. identity_hub-1.0.0a1/tests/conformance/test_credentials_lifecycle.py +121 -0
  164. identity_hub-1.0.0a1/tests/conformance/test_credentials_store.py +111 -0
  165. identity_hub-1.0.0a1/tests/conformance/test_dispatcher_isolation.py +143 -0
  166. identity_hub-1.0.0a1/tests/conformance/test_expected_revision.py +226 -0
  167. identity_hub-1.0.0a1/tests/conformance/test_free_text_channels.py +138 -0
  168. identity_hub-1.0.0a1/tests/conformance/test_heartbeat_batch.py +80 -0
  169. identity_hub-1.0.0a1/tests/conformance/test_heartbeat_history.py +85 -0
  170. identity_hub-1.0.0a1/tests/conformance/test_identities_api.py +162 -0
  171. identity_hub-1.0.0a1/tests/conformance/test_identities_create.py +136 -0
  172. identity_hub-1.0.0a1/tests/conformance/test_identities_update.py +140 -0
  173. identity_hub-1.0.0a1/tests/conformance/test_identity_lost_updates.py +83 -0
  174. identity_hub-1.0.0a1/tests/conformance/test_identity_revision.py +172 -0
  175. identity_hub-1.0.0a1/tests/conformance/test_import_queue.py +111 -0
  176. identity_hub-1.0.0a1/tests/conformance/test_imports_secrets_api.py +155 -0
  177. identity_hub-1.0.0a1/tests/conformance/test_key_rotation.py +121 -0
  178. identity_hub-1.0.0a1/tests/conformance/test_lease_cleanup.py +147 -0
  179. identity_hub-1.0.0a1/tests/conformance/test_lease_credentials.py +91 -0
  180. identity_hub-1.0.0a1/tests/conformance/test_lease_fence.py +96 -0
  181. identity_hub-1.0.0a1/tests/conformance/test_lease_idempotency.py +147 -0
  182. identity_hub-1.0.0a1/tests/conformance/test_lease_rotation.py +69 -0
  183. identity_hub-1.0.0a1/tests/conformance/test_lease_semantics.py +159 -0
  184. identity_hub-1.0.0a1/tests/conformance/test_login_operations.py +127 -0
  185. identity_hub-1.0.0a1/tests/conformance/test_maintenance_timers.py +111 -0
  186. identity_hub-1.0.0a1/tests/conformance/test_memory_parity.py +167 -0
  187. identity_hub-1.0.0a1/tests/conformance/test_metrics_snapshot.py +52 -0
  188. identity_hub-1.0.0a1/tests/conformance/test_normalized_ref.py +153 -0
  189. identity_hub-1.0.0a1/tests/conformance/test_notification_queue.py +180 -0
  190. identity_hub-1.0.0a1/tests/conformance/test_operation_idempotency.py +181 -0
  191. identity_hub-1.0.0a1/tests/conformance/test_override_verification.py +227 -0
  192. identity_hub-1.0.0a1/tests/conformance/test_provider_operations.py +84 -0
  193. identity_hub-1.0.0a1/tests/conformance/test_proxies_pools_api.py +140 -0
  194. identity_hub-1.0.0a1/tests/conformance/test_proxy_fingerprint.py +103 -0
  195. identity_hub-1.0.0a1/tests/conformance/test_proxy_management.py +218 -0
  196. identity_hub-1.0.0a1/tests/conformance/test_retention.py +147 -0
  197. identity_hub-1.0.0a1/tests/conformance/test_secret_boundaries.py +197 -0
  198. identity_hub-1.0.0a1/tests/conformance/test_secret_idempotency.py +122 -0
  199. identity_hub-1.0.0a1/tests/conformance/test_session_modes.py +68 -0
  200. identity_hub-1.0.0a1/tests/conformance/test_session_store.py +96 -0
  201. identity_hub-1.0.0a1/tests/conformance/test_spaces.py +51 -0
  202. identity_hub-1.0.0a1/tests/conformance/test_store.py +72 -0
  203. identity_hub-1.0.0a1/tests/conformance/test_store_coverage.py +167 -0
  204. identity_hub-1.0.0a1/tests/conformance/test_sync_refresh.py +181 -0
  205. identity_hub-1.0.0a1/tests/conformance/test_webhook_claims.py +187 -0
  206. identity_hub-1.0.0a1/tests/conftest.py +118 -0
  207. identity_hub-1.0.0a1/tests/fixtures/__init__.py +0 -0
  208. identity_hub-1.0.0a1/tests/fixtures/api.py +36 -0
  209. identity_hub-1.0.0a1/tests/fixtures/broken_stores.py +134 -0
  210. identity_hub-1.0.0a1/tests/fixtures/lease_resilience.py +235 -0
  211. identity_hub-1.0.0a1/tests/fixtures/services.py +99 -0
  212. identity_hub-1.0.0a1/tests/fixtures/shared_lease.py +66 -0
  213. identity_hub-1.0.0a1/tests/load/test_heartbeat_batch_load.py +40 -0
  214. identity_hub-1.0.0a1/tests/load/test_lease_selection_load.py +66 -0
  215. identity_hub-1.0.0a1/tests/load/test_performance.py +136 -0
  216. identity_hub-1.0.0a1/tests/pg/__init__.py +0 -0
  217. identity_hub-1.0.0a1/tests/pg/helpers.py +27 -0
  218. identity_hub-1.0.0a1/tests/pg/test_acquire.py +163 -0
  219. identity_hub-1.0.0a1/tests/pg/test_admin_state.py +97 -0
  220. identity_hub-1.0.0a1/tests/pg/test_admin_volume.py +88 -0
  221. identity_hub-1.0.0a1/tests/pg/test_aggregates.py +84 -0
  222. identity_hub-1.0.0a1/tests/pg/test_bindings.py +164 -0
  223. identity_hub-1.0.0a1/tests/pg/test_cards.py +119 -0
  224. identity_hub-1.0.0a1/tests/pg/test_credentials_migration.py +121 -0
  225. identity_hub-1.0.0a1/tests/pg/test_db_fixture.py +19 -0
  226. identity_hub-1.0.0a1/tests/pg/test_delivery_reads.py +169 -0
  227. identity_hub-1.0.0a1/tests/pg/test_dispatcher.py +116 -0
  228. identity_hub-1.0.0a1/tests/pg/test_doctor.py +190 -0
  229. identity_hub-1.0.0a1/tests/pg/test_envelope_upgrade.py +44 -0
  230. identity_hub-1.0.0a1/tests/pg/test_event_reads.py +112 -0
  231. identity_hub-1.0.0a1/tests/pg/test_expected_revision_race.py +108 -0
  232. identity_hub-1.0.0a1/tests/pg/test_heartbeat_hot.py +76 -0
  233. identity_hub-1.0.0a1/tests/pg/test_hub.py +130 -0
  234. identity_hub-1.0.0a1/tests/pg/test_human_queue.py +63 -0
  235. identity_hub-1.0.0a1/tests/pg/test_idempotency_migration.py +175 -0
  236. identity_hub-1.0.0a1/tests/pg/test_idempotency_race.py +173 -0
  237. identity_hub-1.0.0a1/tests/pg/test_identities_race.py +53 -0
  238. identity_hub-1.0.0a1/tests/pg/test_identity_data.py +140 -0
  239. identity_hub-1.0.0a1/tests/pg/test_identity_revision_migration.py +94 -0
  240. identity_hub-1.0.0a1/tests/pg/test_import.py +184 -0
  241. identity_hub-1.0.0a1/tests/pg/test_import_jobs.py +95 -0
  242. identity_hub-1.0.0a1/tests/pg/test_last_leased_migration.py +93 -0
  243. identity_hub-1.0.0a1/tests/pg/test_lease_fencing.py +114 -0
  244. identity_hub-1.0.0a1/tests/pg/test_lease_lifecycle.py +191 -0
  245. identity_hub-1.0.0a1/tests/pg/test_lease_logs.py +82 -0
  246. identity_hub-1.0.0a1/tests/pg/test_lease_release_race.py +86 -0
  247. identity_hub-1.0.0a1/tests/pg/test_lease_resilience.py +40 -0
  248. identity_hub-1.0.0a1/tests/pg/test_lease_selection.py +77 -0
  249. identity_hub-1.0.0a1/tests/pg/test_links.py +84 -0
  250. identity_hub-1.0.0a1/tests/pg/test_lock_order.py +66 -0
  251. identity_hub-1.0.0a1/tests/pg/test_logins.py +200 -0
  252. identity_hub-1.0.0a1/tests/pg/test_maintenance.py +233 -0
  253. identity_hub-1.0.0a1/tests/pg/test_metrics.py +56 -0
  254. identity_hub-1.0.0a1/tests/pg/test_migrate_privileges.py +205 -0
  255. identity_hub-1.0.0a1/tests/pg/test_migrations.py +191 -0
  256. identity_hub-1.0.0a1/tests/pg/test_normalized_ref_migration.py +91 -0
  257. identity_hub-1.0.0a1/tests/pg/test_notifications.py +196 -0
  258. identity_hub-1.0.0a1/tests/pg/test_operation.py +207 -0
  259. identity_hub-1.0.0a1/tests/pg/test_outbox.py +127 -0
  260. identity_hub-1.0.0a1/tests/pg/test_overview.py +103 -0
  261. identity_hub-1.0.0a1/tests/pg/test_pgbouncer.py +74 -0
  262. identity_hub-1.0.0a1/tests/pg/test_pool_pick.py +180 -0
  263. identity_hub-1.0.0a1/tests/pg/test_provider_operation_claims.py +40 -0
  264. identity_hub-1.0.0a1/tests/pg/test_proxies.py +158 -0
  265. identity_hub-1.0.0a1/tests/pg/test_proxy_reads.py +66 -0
  266. identity_hub-1.0.0a1/tests/pg/test_quotas.py +104 -0
  267. identity_hub-1.0.0a1/tests/pg/test_read_schema.py +214 -0
  268. identity_hub-1.0.0a1/tests/pg/test_register.py +204 -0
  269. identity_hub-1.0.0a1/tests/pg/test_retention_plans.py +194 -0
  270. identity_hub-1.0.0a1/tests/pg/test_rollups.py +148 -0
  271. identity_hub-1.0.0a1/tests/pg/test_schema.py +116 -0
  272. identity_hub-1.0.0a1/tests/pg/test_schema_check.py +197 -0
  273. identity_hub-1.0.0a1/tests/pg/test_schema_integrity.py +201 -0
  274. identity_hub-1.0.0a1/tests/pg/test_search.py +225 -0
  275. identity_hub-1.0.0a1/tests/pg/test_secrets_admin.py +57 -0
  276. identity_hub-1.0.0a1/tests/pg/test_session_migration.py +109 -0
  277. identity_hub-1.0.0a1/tests/pg/test_session_replace_race.py +60 -0
  278. identity_hub-1.0.0a1/tests/pg/test_snapshots.py +65 -0
  279. identity_hub-1.0.0a1/tests/pg/test_state.py +281 -0
  280. identity_hub-1.0.0a1/tests/pg/test_storage_errors.py +100 -0
  281. identity_hub-1.0.0a1/tests/pg/test_store_time.py +77 -0
  282. identity_hub-1.0.0a1/tests/pg/test_sync.py +134 -0
  283. identity_hub-1.0.0a1/tests/pg/test_verifications.py +155 -0
  284. identity_hub-1.0.0a1/tests/pg/test_webhooks.py +147 -0
  285. identity_hub-1.0.0a1/tests/store_coverage.py +65 -0
  286. identity_hub-1.0.0a1/tests/test_alembic_exclude.py +28 -0
  287. identity_hub-1.0.0a1/tests/test_architecture.py +39 -0
  288. identity_hub-1.0.0a1/tests/test_improvements_traceability.py +68 -0
  289. identity_hub-1.0.0a1/tests/test_library_ci.py +65 -0
  290. identity_hub-1.0.0a1/tests/test_public_api.py +127 -0
  291. identity_hub-1.0.0a1/tests/test_release_fixes_traceability.py +66 -0
  292. identity_hub-1.0.0a1/tests/test_smoke.py +15 -0
  293. identity_hub-1.0.0a1/tests/test_space_surface.py +66 -0
  294. identity_hub-1.0.0a1/tests/test_traceability.py +37 -0
  295. identity_hub-1.0.0a1/tests/unit/__init__.py +0 -0
  296. identity_hub-1.0.0a1/tests/unit/test_adapter_contexts.py +188 -0
  297. identity_hub-1.0.0a1/tests/unit/test_admissibility.py +94 -0
  298. identity_hub-1.0.0a1/tests/unit/test_analytics.py +260 -0
  299. identity_hub-1.0.0a1/tests/unit/test_api_names.py +136 -0
  300. identity_hub-1.0.0a1/tests/unit/test_cli.py +146 -0
  301. identity_hub-1.0.0a1/tests/unit/test_cli_safety.py +302 -0
  302. identity_hub-1.0.0a1/tests/unit/test_conformance_mutations.py +71 -0
  303. identity_hub-1.0.0a1/tests/unit/test_crypto.py +146 -0
  304. identity_hub-1.0.0a1/tests/unit/test_data_query.py +113 -0
  305. identity_hub-1.0.0a1/tests/unit/test_documentation.py +59 -0
  306. identity_hub-1.0.0a1/tests/unit/test_error_codes.py +174 -0
  307. identity_hub-1.0.0a1/tests/unit/test_errors_clock.py +55 -0
  308. identity_hub-1.0.0a1/tests/unit/test_events_catalog.py +137 -0
  309. identity_hub-1.0.0a1/tests/unit/test_examples.py +58 -0
  310. identity_hub-1.0.0a1/tests/unit/test_fake_clock.py +167 -0
  311. identity_hub-1.0.0a1/tests/unit/test_fingerprint_keyring.py +97 -0
  312. identity_hub-1.0.0a1/tests/unit/test_idempotency_fingerprint.py +100 -0
  313. identity_hub-1.0.0a1/tests/unit/test_ids.py +47 -0
  314. identity_hub-1.0.0a1/tests/unit/test_import_jobs.py +115 -0
  315. identity_hub-1.0.0a1/tests/unit/test_import_resilience.py +298 -0
  316. identity_hub-1.0.0a1/tests/unit/test_key_providers.py +141 -0
  317. identity_hub-1.0.0a1/tests/unit/test_lease_resilience.py +86 -0
  318. identity_hub-1.0.0a1/tests/unit/test_logging_context.py +47 -0
  319. identity_hub-1.0.0a1/tests/unit/test_login_resilience.py +301 -0
  320. identity_hub-1.0.0a1/tests/unit/test_maintenance_isolation.py +97 -0
  321. identity_hub-1.0.0a1/tests/unit/test_memory_speed.py +83 -0
  322. identity_hub-1.0.0a1/tests/unit/test_memory_store.py +667 -0
  323. identity_hub-1.0.0a1/tests/unit/test_metrics.py +25 -0
  324. identity_hub-1.0.0a1/tests/unit/test_notification_delivery.py +230 -0
  325. identity_hub-1.0.0a1/tests/unit/test_outbound.py +356 -0
  326. identity_hub-1.0.0a1/tests/unit/test_page_total.py +35 -0
  327. identity_hub-1.0.0a1/tests/unit/test_payload_limits.py +185 -0
  328. identity_hub-1.0.0a1/tests/unit/test_provider_operation_states.py +82 -0
  329. identity_hub-1.0.0a1/tests/unit/test_proxy_health.py +259 -0
  330. identity_hub-1.0.0a1/tests/unit/test_public_contract_docs.py +128 -0
  331. identity_hub-1.0.0a1/tests/unit/test_redaction.py +211 -0
  332. identity_hub-1.0.0a1/tests/unit/test_regions.py +395 -0
  333. identity_hub-1.0.0a1/tests/unit/test_rollups.py +140 -0
  334. identity_hub-1.0.0a1/tests/unit/test_rollups_and_hub_close.py +262 -0
  335. identity_hub-1.0.0a1/tests/unit/test_service.py +125 -0
  336. identity_hub-1.0.0a1/tests/unit/test_service_adapters.py +144 -0
  337. identity_hub-1.0.0a1/tests/unit/test_snapshots.py +86 -0
  338. identity_hub-1.0.0a1/tests/unit/test_space_mixins.py +26 -0
  339. identity_hub-1.0.0a1/tests/unit/test_telegram_transport.py +114 -0
  340. identity_hub-1.0.0a1/tests/unit/test_testing.py +47 -0
  341. identity_hub-1.0.0a1/tests/unit/test_testing_plugin.py +136 -0
  342. identity_hub-1.0.0a1/tests/unit/test_tier_events_metrics.py +119 -0
  343. identity_hub-1.0.0a1/tests/unit/test_timezones.py +26 -0
  344. identity_hub-1.0.0a1/tests/unit/test_verification.py +205 -0
  345. identity_hub-1.0.0a1/tests/unit/test_webhook_delivery.py +155 -0
  346. identity_hub-1.0.0a1/tests/unit/test_webhooks.py +67 -0
@@ -0,0 +1,35 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .hypothesis/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .import_linter_cache/
9
+ .uv-cache/
10
+ .coverage
11
+ .coverage.*
12
+ coverage.xml
13
+ htmlcov/
14
+ dist/
15
+ build/
16
+ *.egg-info/
17
+ .idea/
18
+ .vscode/
19
+ .env
20
+ .env.*
21
+ !.env.example
22
+ .claude/scheduled_tasks.lock
23
+ .claude/settings.local.json
24
+ .check.log
25
+ admin/.venv/
26
+ admin/.mypy_cache/
27
+ admin/.pytest_cache/
28
+ admin/.ruff_cache/
29
+ admin/frontend/node_modules/
30
+ admin/frontend/dist/
31
+ admin/src/identity_hub_admin/static/
32
+ admin/test-results/
33
+ playwright-report/
34
+ test-results/
35
+ *.log
@@ -0,0 +1,521 @@
1
+ # Changelog
2
+
3
+ Значимые пользовательские изменения Identity Hub фиксируются в этом файле.
4
+ Формат основан на Keep a Changelog; до первого стабильного релиза API может
5
+ меняться без периода совместимости.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [1.0.0a1] - 2026-10-12
10
+
11
+ Первый публичный альфа-релиз. API может меняться между альфа-версиями. Изменения ниже
12
+ описаны относительно внутреннего среза от 2026-09-30.
13
+
14
+ Исправлены 118 из 125 находок предрелизного ревью; что чем закреплено — раздел «Статус
15
+ исправлений» в `docs/review/PRE-RELEASE-REVIEW.md`, оставшиеся 7 описаны в
16
+ [ограничениях](docs/guide/limitations.md#известные-дефекты).
17
+
18
+ ### Added
19
+
20
+ - Очередь операций провайдера (`IMP-11`, `D-220`, миграция `0024`): машина состояний и
21
+ `RetryPolicy` в `identity_hub.domain.provider_operations`; Store `enqueue_provider_operation`,
22
+ `claim_provider_operations`, `apply_provider_operation`, `recover_provider_operations`,
23
+ `cancel_provider_operation`. Гарантия — at-least-once попытка. `hub.start()` требует ревизию
24
+ схемы `0024`.
25
+ - Режимы записи сессии (`IMP-10`, `D-219`): `lease.save_session(..., mode=)` и
26
+ `secrets.replace_session(..., mode=)` — `in_place` (по умолчанию) или `replace` (новая запись,
27
+ прежняя в истории); `secrets.revoke_session` — отзыв без замены. Вход сохраняет сессию в режиме
28
+ `replace`. События `session.saved` и `identity.session.updated` несут `mode`, `session_id`,
29
+ `replaced_id`, `session_revision` — без данных сессии.
30
+ - `CredentialInput` и `credential_records` у `IdentityCreate`, `ImportRecord` (в том числе
31
+ отложенного импорта) и `make_identity`; `AccessCard.credential_records` — все записи без
32
+ секретов (`IMP-09`, `D-217`).
33
+ - `lease.credential(kind, name)`, `lease.active_credentials()`, `ctx.credential(kind, name)` в
34
+ контекстах входа и проб, `SecretRef.credential(kind, name)` для раскрытия с аудитом
35
+ (`IMP-09`, `D-216`). `lease.credentials()` остаётся доступом к записи `default` и, как и
36
+ `credential()`, не отдаёт выключенную, отозванную или истёкшую запись.
37
+ - Команды нескольких credentials (`IMP-09`, `D-215`): `space.secrets.put_credential`,
38
+ `credentials` (метаданные без секрета, `Credential` с `expired` по времени),
39
+ `disable_credential`, `enable_credential`, `revoke_credential`; `revoked` — конечное состояние.
40
+ - `idempotency_key` у `secrets.replace_credentials` и `secrets.replace_session`: секрет входит в
41
+ отпечаток команды только HMAC-тегом ключом отпечатков, после ротации повтор сверяется прежним
42
+ ключом (`IMP-04`, `D-208`, миграция `0019`). `hub.equivalence_tag(value)` — HMAC-тег значения
43
+ для сравнения чувствительных данных без их хранения.
44
+ - Кольцо ключей отпечатков (`IMP-04`, `D-207`): HMAC-SHA-256 отдельными ключами, не KEK.
45
+ `StaticSecretsProvider(fingerprint_keys=, fingerprint_active=)`, переменная
46
+ `IDENTITY_HUB_FINGERPRINT_KEYS`, `FileSecretsProvider(fingerprint_path=)`; без них ключи
47
+ выводятся из KEK через HKDF. `hub.doctor.rekey_fingerprints()` и
48
+ `identity-hub doctor rekey-fingerprints` пересчитывают отпечатки прокси.
49
+ - `expected_revision` у команд изменения аккаунта (`space.identities`, `space.secrets`,
50
+ `space.links`, `space.verifications`): устаревшая ревизия — `RevisionConflict` без изменений,
51
+ в том числе для параллельной команды в другой сессии (`IMP-07`, `D-206`). Руководство —
52
+ [«Аккаунты»](docs/guide/03-accounts.md#параллельные-правки-expected_revision).
53
+ - Раздел «Что входит в публичный контракт» и таблица значений по умолчанию в
54
+ [«Карте API»](docs/guide/api.md) (тест сверяет таблицу с кодом); краткий раздел README на
55
+ английском: назначение, установка, статус альфа, язык документации.
56
+ - `SecretValue`: секрет со скрытым `repr`/`str`, без JSON и pickle; scrubber событий его отвергает.
57
+ - Ресурсные неймспейсы пространства (`space.identities`, `space.imports`, `space.secrets`,
58
+ `space.proxies`, `space.pools`, `space.auth`, `space.sync`, `space.links`, `space.data`,
59
+ `space.verifications`, `space.admin`, `space.analytics`, `space.maintenance`) с единой
60
+ конвенцией глаголов; карта — [«Карта API»](docs/guide/api.md).
61
+ - `LoginOutcome` (`StrEnum`, значения прежних строк), `LoginReport`, `ReconcileReport` в корне пакета.
62
+ - `hub.read`: публичная read-схема на SQLAlchemy Core (`identities`, `leases`, `lease_history`,
63
+ `region_states`, `events`, `proxies`) без секретов для join с таблицами приложения и `among=`;
64
+ версия `READ_SCHEMA_VERSION`.
65
+ - Руководство на новом API и страница [«Карта API»](docs/guide/api.md): точка входа, неймспейсы,
66
+ типовые операции по сущностям, изменение состояний и лимиты.
67
+ - `Identity`, `IdentityCreate`, `IdentityFilter`, `Page`, `CreateManyResult`, `SecretRef`, `Proxy`.
68
+ - Стабильные коды ошибок `IdentityHubError.code` и безопасный `context`; функции `http_status()`
69
+ и `exit_code()`; новые типы `RevisionConflict`, `IdempotencyConflict`, `LeaseOwnershipLost`,
70
+ `LeaseRevoked`, `LeaseExpired`, `DatabaseBusy`, `SchemaIncompatible`,
71
+ `SecretConfigurationError`. CLI печатает `error[<code>]` и возвращает код выхода по таблице.
72
+
73
+ - `IdentityHub(redact_refs_in_events=True)`: `external_ref` и `labels` не попадают в события и
74
+ вебхуки. `LeaseLogPolicy.max_field_chars`, `max_entry_bytes`, `buffer_ttl`.
75
+ - `await hub.aclose()` и `async with hub:` — штатное завершение: запись накопленных метрик и
76
+ потерь аренд, закрытие HTTP-клиента Telegram, созданного хабом. `hub.flush_metrics()` — запись
77
+ телеметрии операций в процессе без dispatcher.
78
+ - `identity-hub rollups --rebuild --force` — перестроить диапазон старше срока хранения событий.
79
+ - `identity-hub doctor renormalize --service <имя>` — пересчёт `normalized_ref` из командной строки.
80
+ - `verifications.review(..., "reset_attempts")` — обнулить счётчик попыток фактора.
81
+ - `imports.run(..., max_identities=3)` — ёмкость прокси, создаваемых импортом.
82
+ - `space.proxies.replace_credentials()`, `move()`, `delete(unbind=)`; события `proxy.updated`
83
+ и `proxy.deleted`.
84
+ - `space.proxies.recheck_group(group, checker, actor=...)`; `ProxyHealthPolicy.jitter`,
85
+ `check_timeout`, `check_concurrency`; событие `proxy.checker.failed`.
86
+ - `Service(probe_timeout=...)` (по умолчанию 60 с) и `ReconcileReport.skipped_unavailable`.
87
+ - `Service(login_timeout=...)` (по умолчанию 120 с) и `space.maintenance.expire_logins()`:
88
+ `logging_in` без живой аренды возвращается в `no_session`.
89
+ - Пауза канала уведомлений после ответа 429 (`ih_notification_channels.paused_until`,
90
+ миграция `0012`).
91
+ - `WebhookPolicy.concurrency` (по умолчанию 8) — число endpoint'ов, обслуживаемых одновременно.
92
+ - `hub.events.subscribe(..., max_attempts=5)` и журнальное событие `dispatcher.event.skipped`.
93
+ - `Clock.sleep()` и `Clock.idle()`: ожидание аренды, heartbeat и опрос диспетчера идут по часам
94
+ хаба. Под `FakeClock` `wait=` истекает, а `advance()` будит heartbeat активных аренд.
95
+ - `hub.doctor.inactive_keys()` и поля `RotationReport.remaining`, `unreadable`,
96
+ `unreadable_total`, `complete`.
97
+ - `FileSecretsProvider.reload()` — перечитать файл мастер-ключей без перезапуска процесса.
98
+ - Событие `proxy.secret.revealed` (аудит `space.proxies.url`).
99
+ - `WebhookPolicy.proxy` — явный исходящий прокси доставки вебхуков; ошибка доставки
100
+ `ForbiddenTarget`, если получатель резолвится в приватный или служебный адрес.
101
+ - `StorageError` (код `storage_error`): ошибка хранилища без SQL и параметров запроса;
102
+ `DatabaseBusy.sqlstate`.
103
+ - `Operation.previous_outcome` — исход прошлой попытки с тем же `key`
104
+ (`"succeeded"`, `"failed"` или `None`).
105
+
106
+ ### Changed
107
+
108
+ - Вход идёт через очередь операций провайдера (`IMP-11`, `D-222`): claim коммитится до вызова
109
+ `Authenticator`, итог — одной транзакцией с проверкой claim и fence; поздний ответ зависшего
110
+ воркера ничего не пишет. `ctx.attempt_id` — ключ идемпотентности попытки,
111
+ `LoginReport.operations` — операции попыток. Store: `claim_provider_operations(operation_ids=)`.
112
+ - Сессии хранят историю (`IMP-10`, `D-218`, миграция `0023`): у записи есть `id` и ревизия, в
113
+ слоте одна не отозванная запись. `SessionStatus`: `created`, `active`, `invalidated`,
114
+ `revoked` (`VALID`/`INVALID` — псевдонимы `ACTIVE`/`INVALIDATED`, `EXPIRED` удалён; прежние
115
+ строки переведены миграцией). Store: `put_session(..., mode="in_place"|"replace")` возвращает
116
+ `SessionWrite` или `None`, `revoke_session`, `session_history`. `hub.start()` требует ревизию
117
+ схемы `0023`.
118
+ - `ih_credentials` хранит несколько записей на аккаунт (`IMP-09`, `D-214`, миграция `0022`): вид
119
+ (`kind`), имя (`name`), открытая часть, жизненный цикл, срок и ревизия; прежняя запись стала
120
+ `default`/`default` без перешифрования. `Service.credential_kinds` объявляет виды кроме `default`
121
+ (`credential_schema`, `credential_key`); Store: `put_credential`, `get_credential`,
122
+ `list_credentials`; `credential_associated_data` в `identity_hub.crypto`. `hub.start()` требует
123
+ ревизию схемы `0022`.
124
+ - Семантика renew/release/revoke (`IMP-06`, `D-213`): heartbeat не укорачивает срок
125
+ (`GREATEST`); `release()` отозванной аренды бросает `LeaseRevoked`, освобождение после
126
+ естественного истечения закрывает историю как `expired`; `pools.revoke()` сразу будит аренду
127
+ этого же процесса. Store: `release_lease` возвращает исход (`released`/`expired`/`closed`/
128
+ `revoked`) вместо `bool` — собственные реализации Store нужно обновить.
129
+ - `LeaseLost.body_completed`: потеря аренды после успешного тела `async with pool.lease()`
130
+ (`R1-16`).
131
+ - Heartbeat аренд процесса продлевается пачкой: одновременные продления уходят одним
132
+ `UPDATE ... WHERE token = ANY(...)` (`IMP-02`, `D-211`, Store `renew_leases`). Отзыв или потеря
133
+ касается только своей аренды. Замер: 272 продления/с поштучно, 21 тыс./с пачками по 100,
134
+ 58 тыс./с по 500.
135
+ - Heartbeat эксклюзивной аренды — HOT-обновление (`IMP-02`, `D-210`): миграция `0021` удаляет
136
+ индексы `ix_ih_leases_expires_at` и `ix_ih_lease_history_open`, у `ih_leases` — `fillfactor = 70`.
137
+ История аренды получает время heartbeat и счётчики раз в `LeaseTiming.stats_flush` (по
138
+ умолчанию 5 мин, `pools.define(stats_flush=)`) и при освобождении; список живых аренд берёт
139
+ heartbeat из строки аренды. Store: `extend_lease(..., flush=)`.
140
+ - Выборка аренды — по времени последнего захвата в строке аккаунта (`last_leased_at`, миграция
141
+ `0020`) и индексу в том же порядке (`IMP-01`, `D-209`): занятые аккаунты не перебираются, ротация
142
+ равномерная и для разделяемых аренд. Раньше порядок шёл по строке живой аренды и после
143
+ освобождения терялся. `hub.start()` требует ревизию схемы `0020`.
144
+ - `Identity.revision`, `IdentityCard.revision` и `hub.read.identities.c.revision` — ревизия
145
+ агрегата: растёт на 1 за команду, изменившую аккаунт, а не на каждое событие (`IMP-07`,
146
+ `D-205`). Аренда, операции, прокси и раскрытие секрета её больше не меняют. Миграция `0017`
147
+ переносит прежние значения (`GREATEST(event_seq, 1)`), `READ_SCHEMA_VERSION` — 2 (`R2-D23`).
148
+ `hub.start()` требует ревизию схемы `0017`.
149
+ - В событии — поле `revision` (ревизия аккаунта после команды): `Event.revision`, тело вебхука,
150
+ журнал админки, колонка `hub.read.events.c.revision`. `StoreTx.lock_identity_revision` при
151
+ расхождении бросает `RevisionConflict` (наследник `ConcurrentModification`); `doctor`
152
+ проверяет ревизию по последнему событию.
153
+
154
+ - `ProbeContext.external_ref`; `LoginContext.session(slot)` — прежняя сессия при повторном входе,
155
+ в том числе признанная недействительной. `FileImporter.read` может быть async-генератором,
156
+ обычной функцией или `async def`, возвращающими список. `imports.run_files(base_dir=,
157
+ max_bytes=)`: путь вне каталога и файл больше лимита попадают в `failed`. Примеры и руководство
158
+ типизируют `ctx: LoginContext` / `ctx: ProbeContext` и обходятся без заглушек.
159
+ - Адаптеры сервиса проверяются при объявлении `Service`: метод на месте, `async def`, у
160
+ `FileImporter.read` — async-генератор; сообщение называет адаптер и нужную сигнатуру.
161
+ `Authenticator` требует только `login` (`refresh` — необязательный `SessionRefresher`);
162
+ `probes` — любое подмножество `auth`/`standing`/`verification` (`AuthProbe`, `StandingProbe`,
163
+ `VerificationProbe`). `Service(authenticator=login_fn)` и
164
+ `Service(error_classifier={TimeoutError: Fault(...)})`; классификатор получает вид операции,
165
+ если `classify` принимает `kind`. Протокол `EventConsumer` удалён: `subscribe` принимает
166
+ `Callable[[Event], Awaitable[None]]`.
167
+ - **Несовместимо:** `actor` — обязательный именованный параметр во всех методах неймспейсов,
168
+ которые записывают, кто выполнил действие: `identities.create/ensure/create_many/update/delete`,
169
+ работа с тегами, профилем, атрибутами, метками и заметкой, `links.add/remove`,
170
+ `imports.run/run_files/stage`, `proxies.report_failure`, `verifications.requirements.add/resolve`,
171
+ `auth.answer_challenge`. Молчаливых `"system"`, `"platform"`, `"worker"` больше нет.
172
+ - **Несовместимо:** `sync.refresh` возвращает `RefreshResult` (`outcomes` — исход по региону),
173
+ `proxies.recheck_due` и `proxies.recheck_group` — `RecheckReport` (`statuses` — новый статус
174
+ каждого прокси).
175
+ - **Несовместимо:** `IdentityCreate.auth` вместо `auth_state` — как в `Identity` и `IdentityFilter`.
176
+ - `LeaseUnavailable` отображается в HTTP 503 (временная недоступность) вместо 404; необязательный
177
+ `retry_after` попадает в `context`.
178
+ - `proxies.create_many(on_conflict=...)` принимает `OnConflict`; `imports.run_files` — явные
179
+ параметры, как у `imports.run`, вместо `**options`; `among` в пулах типизирован:
180
+ `Select | Iterable[UUID] | Callable[[Identity], bool]`.
181
+ - `space.webhooks` и `space.notifications` — неймспейсы, как остальные: один объект на пространство.
182
+ - **Несовместимо:** `hub.space(slug)` больше не создаёт пространство. Если его нет, первая
183
+ операция даёт `NotFound` — опечатка в slug не превращается в пустое пространство. Создание —
184
+ `hub.spaces.create(slug, title)` (уже есть — `AlreadyExists`), `hub.spaces.ensure(slug, title)`
185
+ или `hub.space(slug, title, create=True)`; поиск — `hub.spaces.get(slug)`.
186
+ - CI проверяет, что тесты вызывают каждый метод контракта `Store` на `MemoryStore` и PostgreSQL
187
+ (`IH_STORE_COVERAGE=1` для полного прогона).
188
+ - `StoreConformanceSuite` проверяет изоляцию пространств, учёт живой аренды при выборке, отзыв
189
+ аренды и fencing, атомарность квот, полный откат транзакции, курсор поиска и доставку только
190
+ внешних событий с соблюдением `limit`. Контракт `Store` объявлен внутренним и нестабильным до
191
+ 1.0 (руководство, раздел «Свой адаптер хранилища»).
192
+ - Тестовый инструментарий: `TestingModel` (модели сервиса из `make_service` по умолчанию)
193
+ отвергает неизвестные поля; `make_identity(authenticated=False, session=...)` —
194
+ `ValidationFailed`. `pool.lease(among=...)` принимает предикат от `Identity`.
195
+ - Поиск аккаунтов: строки сортируются по кодам символов (`COLLATE "C"`), а не по локали базы;
196
+ поле профиля (`profile:<key>`) сортируется как текст на обоих хранилищах; строка запроса
197
+ длиннее 1000 символов или с более чем 50 условиями — `ValidationFailed`.
198
+ - `hub.start()` принимает схему любой ревизии из `SCHEMA_COMPATIBLE` (сейчас `0014`–`0016`), а не
199
+ только последней: новый код можно выкатить до `migrate`, обновление идёт без простоя.
200
+ Сообщение `SchemaIncompatible` говорит, что делать: `identity-hub migrate` или обновить пакет.
201
+ - `migrate`: каждая ревизия применяется отдельной транзакцией (сбой не откатывает предыдущие),
202
+ блокировка от параллельного запуска держится на сессии — запускайте миграции через прямое
203
+ подключение, не через PgBouncer в режиме транзакций. Имя `db_schema` проверяется как
204
+ идентификатор (`ValidationFailed`). Миграция `0016` пересчитывает `ih_proxies.bound_count`
205
+ из привязок.
206
+ - Миграция `0015` и чистка по срокам хранения. Обслуживание теперь удаляет квитанции записи
207
+ метрик, закрытые вопросы платформы, часовые и суточные корзины аналитики — новые поля
208
+ `Retention.metric_batches` (7 дней), `closed_challenges` (30 дней), `rollup_hours` (90 дней),
209
+ `rollup_days` (730 дней). После обновления первое суточное обслуживание удалит часовые корзины
210
+ старше 90 дней и суточные старше двух лет: если они нужны дольше, задайте сроки явно.
211
+ Удаление идёт пачками по `MaintenanceSchedule.cleanup_batch_size` строк в отдельных
212
+ транзакциях. Вместе с событием удаляются его доставки вебхуков.
213
+ `Store.cleanup_maintenance` получил параметр `batch_size`.
214
+ - Миграция `0014`. Доставки вебхуков отключённого endpoint'а получают статус `paused`
215
+ (`DeliveryStatus.PAUSED`) и возвращаются в `pending` при включении endpoint'а или `retry`.
216
+ Добавлены внешние ключи `ih_notifications.channel_id` и `ih_verifications.via_identity_id`,
217
+ ограничения на числа прокси (`port` 1–65535, `max_identities >= 1`, `bound_count >= 0`) и индексы
218
+ под внешние ключи. Прокси с портом вне диапазона остановит миграцию — исправьте порт заранее.
219
+ URL прокси с портом 0 отклоняется с `ValidationFailed`.
220
+ - Ключ операции `lease.operation(kind, key=...)` действует в пределах пространства, а не всей
221
+ базы (миграция `0009`). Повтор после неуспешной попытки — новая попытка со списанием квоты
222
+ (`duplicate=False`, `previous_outcome="failed"`); после успешной — `duplicate=True`. Тот же
223
+ ключ с другим видом операции — `IdempotencyConflict`. Запись ключа переживает удаление
224
+ аккаунта и хранится `Retention.operation_keys` с момента последней попытки.
225
+ - В `last_error` истории аренды и в `data.error` событий `lease.*` сохраняется только имя типа
226
+ исключения, а не его текст. `raw_status`, `code`, `hint` и причины команд обрезаются и очищаются
227
+ от URL с учётными данными, query-строк и `Bearer …`.
228
+ - `scrub` событий сравнивает главное слово имени поля (`tokens_used`, `password_changed_at` больше
229
+ не вырезаются; `api_key`, `authorization`, `jwt`, `otp`, `passphrase`, `proxy` — вырезаются),
230
+ проверяет строки в списках и заменяет объект неизвестного типа его именем. Те же правила и лимит
231
+ размера действуют для полей `extra` в `ih_lease_logs`.
232
+ - `MemoryStore()` без `clock` принимает часы хаба, хаб без `clock` — часы хранилища; разные
233
+ часы — `ValidationFailed`. Свой `Clock` должен реализовать `sleep()` и `idle()`.
234
+ - `space.identities.delete()` при живой аренде бросает `ValidationFailed`; `revoke=True` отзывает
235
+ аренду и закрывает её историю в той же транзакции.
236
+ - `lease.expired` от обслуживания несёт одинаковые поля на обоих хранилищах: `pool`, `holder`,
237
+ `fencing`, `expired_at`.
238
+ - `lease.release(reason=...)` принимает только значения `LeaseEndReason`; другая строка —
239
+ `ValidationFailed` до остановки heartbeat. После `release()` `lease.check()` бросает `LeaseLost`,
240
+ даже если запись об освобождении не прошла.
241
+ - `proxy.pool.exhausted` пишется один раз на вызов `acquire` и не чаще раза в минуту на пул в
242
+ процессе.
243
+ - `hub.doctor.rotate_key()` проходит все наборы до конца за один вызов (`limit` — размер пачки)
244
+ и переоборачивает незавершённые фоновые импорты. CLI `rotate-key` печатает остаток и
245
+ нечитаемые строки и возвращает 1, если под прежним ключом что-то осталось; `doctor`
246
+ сообщает о строках под неактивным ключом.
247
+ - CLI: `rollups --rebuild`, `rollups --purge-minutes` и `snapshots --purge` требуют `--yes`;
248
+ без него — код 2 без подключения к базе. Сбой фабрики и непредвиденная ошибка команды —
249
+ код 1 и `error[factory_failed]` / `error[internal]` с именем типа исключения (раньше
250
+ `ValueError`/`TypeError` печатались текстом как ошибка аргументов с кодом 2). `migrate`
251
+ предупреждает о пароле в `--url`.
252
+ - Rollups: `rebuild` диапазона старше `Retention.events` — `ValidationFailed` без `force=True`
253
+ (в CLI — `--force`); телеметрию (`telemetry.*`) перестройка больше не удаляет. Буфер метрик
254
+ ограничен 10 000 ключей: при переполнении отбрасываются самые старые минуты. Контракт `Store`:
255
+ новый `event_rollup_rows(space_id, bucket_size, start, end)`.
256
+ - `Override`: ключ региона проверяется — для `standing`, `availability`, `lifecycle` он пуст,
257
+ для `auth` по умолчанию `"default"`, для `verification` — объявленный фактор; иначе
258
+ `ValidationFailed`. `key` может быть `None`.
259
+ - Верификации: `approve` в режиме `MANUAL` — только из `under_review`; снятие `verified` по
260
+ наблюдению требует двух наблюдений подряд; фактор с вышедшим сроком действия не даёт допуска
261
+ до прогона обслуживания. Контракт `Store`: новый `expired_factors(identity_id)`.
262
+ - Импорт: несуществующая связь и переполненный прокси отклоняют только свою запись; фоновый
263
+ `update_session` учитывает `verify_sessions` и для существующих аккаунтов (`auth → no_session`);
264
+ `identity.imported` пишется и для пачки из одних обновлений; `run_files` кладёт ошибку файла
265
+ или импортёра в `failed`; причина `ValidationFailed` схемы называет поля.
266
+ - Контракт `Store`: новые `add_proxy_error`, `proxy_by_fingerprint`, `set_proxy_secret`,
267
+ `delete_proxy`.
268
+ - Прокси: `dead` автоматически перепроверяется раз в `max_backoff`; `recheck_due` проверяет
269
+ параллельно; закрепление (`pin_for`) защищает от плановых перепроверок, перехода в `dead` и
270
+ таймера региона. Контракт `Store`: `due_proxies(limit, pool=)` отдаёт и `dead`, без
271
+ закреплённых.
272
+ - Таймеры обслуживания берут только состояния с переходом по истечении (`limited`, `paused`,
273
+ `cooling`); `until` у `frozen`, `banned` и `quarantined` — справочный. `MemoryStore` больше не
274
+ выдаёт в аренду `quarantined` с истёкшим `until`.
275
+ - `sync.refresh_many` сверяет только `ready`-аккаунты, очередь идёт по времени последней
276
+ попытки (колонка `ih_identities.probe_attempted_at`, миграция `0013`); недоступные аккаунты —
277
+ в `skipped_unavailable`, без расхода `limit`. `ProbeResult` отклоняет время без таймзоны.
278
+ - Контракт `Store`: новый `mark_probe_attempts(ids)`; `stale_identities` отдаёт только `ready`.
279
+ - Вход: `auth.login` занятого аккаунта сразу бросает `LeaseUnavailable` (раньше ждал TTL);
280
+ `logging_in → session_invalid` — допустимый переход; ответ на challenge стирается при его
281
+ закрытии; override или команда из `challenge`/`logging_in` закрывает открытый challenge.
282
+ - Контракт `Store`: новый `stuck_logins(limit)`; `resolve_challenge` стирает шифртекст ответа.
283
+ - Уведомления: ответ 429 не увеличивает номер попытки; дайджест отправляется частями до 4096
284
+ символов, одиночное сообщение обрезается с «…»; шаблон с вложенным полем в формате
285
+ отклоняется; `update_telegram_channel(thread_id=None)` сбрасывает тему; `rules()` отсортированы
286
+ по имени.
287
+ - Контракт `Store`: `finish_notification(..., pause_channel=)`.
288
+ - `WebhookPolicy`: `timeout` обязан быть меньше `claim_for`. Pump-ы диспетчера (вебхуки,
289
+ уведомления, импорт, обслуживание) выполняются одновременно.
290
+ - Контракт `Store`: новый `release_webhook_deliveries(claims, retry_after=)`.
291
+ - Контракт `Store`: обработчик `process_events` может вернуть число обработанных событий —
292
+ курсор встаёт за последним из них.
293
+ - Контракт `Store`: `rotate_key(envelope, table=, limit=, after=)` → `RotationBatch`,
294
+ новый `inactive_key_usage(active)`.
295
+ - Мастер-ключи: `key_id` — `[A-Za-z0-9_.-]{1,64}`; ключ из одинаковых байтов в
296
+ `IDENTITY_HUB_KEK` и файле отклоняется; BOM игнорируется; `keys()` провайдера — отображение
297
+ только для чтения; провайдер нельзя сериализовать `pickle`.
298
+ - Публичный контракт `IdentityHub` зафиксирован ([карта API](docs/guide/api.md#контракт-хаба)):
299
+ `store`, `envelope`, `import_runner`, `metric_rollups`, `rollups`, `state_snapshots`,
300
+ `webhook_dispatcher`, `notification_dispatcher`, `metrics_reporter` стали приватными. Добавлены
301
+ `hub.sessionmaker` и `hub.webhook_policy`.
302
+ - `Lease.proxy` возвращает `SecretValue`; строка URL — `lease.proxy.reveal()`. `LoginContext.proxy_url`
303
+ и `ProbeContext.proxy_url` остаются строкой.
304
+ - `space.proxies.url(proxy_id, *, actor, reason)` и `space.secrets.reveal(..., actor, reason)`:
305
+ автор и причина обязательны и непусты.
306
+ - `lease.check()`, `credentials()`, `session()`, `save_session()` и `lease.proxy` после освобождения
307
+ аренды бросают `LeaseLost`; чтение секретов сверяет fencing и замечает отзыв до heartbeat.
308
+ - `repr` `ImportRecord`, `ParsedProxy` и `LeaseRow` не содержит credentials, сессий, URL и пароля
309
+ прокси, `token` аренды.
310
+ - `WebhookEndpoint.url` — `scheme://host/***`: путь и query URL вебхука наружу не отдаются.
311
+ Имя получателя без точки (`redis`, `metadata`) отклоняется при создании endpoint'а.
312
+ - HTTP-клиенты библиотеки (вебхуки, Telegram) не читают `HTTP_PROXY`/`HTTPS_PROXY`/`ALL_PROXY`.
313
+ - `idempotency_key` аренды действует в пределах пространства и принадлежит одной команде: пулу,
314
+ сервису и `holder`. Повтор от другого пула или `holder` — `IdempotencyConflict` (миграция `0010`).
315
+ - Сбой записи результата после успешно выполненной операции не выходит к вызывающему:
316
+ предупреждение в лог и метрика `identity_hub_failures_total{scope="operation_bookkeeping"}`.
317
+ - Контракт `Store`: `charge_quotas(..., idempotency=IdempotencyRequest)`,
318
+ `claim_idempotency_key`, `complete_idempotency_key` вместо `operation_key`/`finish_operation_key`.
319
+ - Уникальность identity — `(service, normalized_ref)`: `Service.normalize_ref` и
320
+ `normalize_ref_version`, миграция `0008`, `hub.doctor.renormalize()` для смены нормализатора.
321
+ - `hub.start()` проверяет ревизию схемы и бросает `SchemaIncompatible`, если она не та, что
322
+ ожидает код; `identity-hub migrate` берёт advisory lock на схему.
323
+ - Associated data шифрования в формате `identity-hub:v1:…`; записи среза от 2026-09-30 читаются,
324
+ `rotate-key` переводит их в новый формат. Лимиты: JSON до 64 KiB и 16 уровней, секрет до 512 KiB.
325
+ - Единый порядок блокировок ([`docs/lock-order.md`](docs/lock-order.md)) и
326
+ `StoreTx.lock_identities()`; проверка границ импортов `lint-imports` в `scripts/check.sh`.
327
+ - `Pool.lease()` и `Pool.acquire()` принимают явные параметры: опечатка в имени — `TypeError`.
328
+ - `DecryptionFailed` наследует `SecretConfigurationError` (код `decryption_failed`).
329
+
330
+ ### Fixed
331
+
332
+ - `MemoryStore` ведёт `version` и `updated_at` credentials и сессий так же, как PostgreSQL
333
+ (`R2-P08`).
334
+ - Полный fence аренды (`IMP-05`, `D-212`, `R1-15`): запись и чтение секретов от имени аренды
335
+ сверяют `lease_id`, `token`, точное равенство fencing и живую аренду под блокировкой строки
336
+ аккаунта. Истёкшая аренда, которую ещё никто не перехватил, больше не пишет; отказ —
337
+ `LeaseOwnershipLost` (наследует `LeaseLost` и `StaleFencing`). `lease.fence` и `LeaseFence` в
338
+ корне пакета, Store `check_fence`.
339
+ - Админка: отпечаток идемпотентности чувствительных команд (токен канала уведомлений, ответ на
340
+ challenge) считался по значениям, уже замаскированным `SecretStr`, поэтому
341
+ повтор с тем же `Idempotency-Key` и другим секретом отдавал прежний результат вместо 409.
342
+ Теперь секрет входит в отпечаток HMAC-тегом `hub.equivalence_tag` (`D-208`).
343
+ - Отпечаток прокси — HMAC ключом отпечатков вместо SHA-256 без ключа: логин прокси больше не
344
+ подбирается перебором по известному хосту (`R2-S16`). Миграция `0018` добавляет
345
+ `ih_proxies.fingerprint_key_id`; прежние отпечатки находятся до пересчёта
346
+ `identity-hub doctor rekey-fingerprints`. `hub.start()` требует ревизию схемы `0018`.
347
+ `identity_hub.proxies.fingerprint` заменён на `fingerprint_value` и `legacy_fingerprint`.
348
+ - `Page.total` у `identities.list`/`search` описан верно: точное число до 1000, больше — 1001;
349
+ точное число до 1 000 000 — `identities.count()` (`R1-28`).
350
+ - Транзакция `MemoryStore` больше не копирует всё состояние: цикл аренды с операцией при 1000
351
+ аккаунтах — около 5 мс вместо 600.
352
+ - `MemoryStore` ведёт себя как PostgreSQL: удаление аккаунта освобождает место на прокси;
353
+ доказательства верификации читаются после `submit`; события и аренды одного момента
354
+ упорядочены (UUIDv7); фильтр профиля `{"key": None}` не совпадает с отсутствующим полем;
355
+ связи идут в том же порядке и с временем связи, путь `links.impact` — от исходного аккаунта;
356
+ открытая аренда учитывается в `lease_stats` как `active`.
357
+ - Фикстура `identity_hub` pytest-плагина работает в strict-режиме pytest-asyncio (режим по
358
+ умолчанию), а без pytest-asyncio тест останавливается с понятной ошибкой.
359
+ - `among=select(...)` на `MemoryStore` — `ValidationFailed` с подсказкой вместо `TypeError`.
360
+ - Один `MemoryStore` работает в нескольких `asyncio.run()` подряд и при конкурентных транзакциях.
361
+ - `AlreadyExists` при совпадении ref с аккаунтом другого пространства больше не содержит его id
362
+ (`existing_id` пуст): раньше это было утечкой между пространствами.
363
+ - Границы очистки минутных корзин и снимков, час снимка и минуты метрик считаются по времени
364
+ базы, как и корзины событий; расхождение часов процесса и базы больше не сдвигает их.
365
+ - `new_id()` строго растёт в пределах процесса при любой реализации UUIDv7; минимальная версия
366
+ `uuid-utils` (Python до 3.14) — 1.0.
367
+ - Рецепт подключения через PgBouncer в режиме транзакций в `docs/integrations.md` исправлен:
368
+ прежний (`prepared_statement_cache_size: 0`) давал ошибки `prepared statement … does not
369
+ exist` под нагрузкой. Рецепт проверяется в CI.
370
+ - `dispatcher` за PgBouncer больше не держит соединение под LISTEN, на который уведомления не
371
+ приходят: подписка проверяется пробным уведомлением, при неудаче диспетчер освобождает
372
+ соединение, работает опросом и пишет об этом в лог.
373
+ - `hub.background_snapshot(allowed_spaces=[...])` больше не падает на PostgreSQL с `TypeError`.
374
+ - Очередь оператора открывается при любом числе аккаунтов «на внимание»: раньше после 32 767
375
+ запрос упирался в предел параметров asyncpg. Списки id передаются в запросы одним
376
+ параметром-массивом.
377
+ - `doctor.check` читает аккаунты страницами по 1000 и сверяет их с регионами за линейное время;
378
+ раньше грузил всю базу в память и работал квадратично (14 000 аккаунтов — 139 с).
379
+ - Вторая страница списка аккаунтов при сортировке по числовому или булеву полю профиля больше
380
+ не падает; изменённый курсор даёт `ValidationFailed`, а не ошибку базы.
381
+ - `id:<hex>` длиннее 12 символов находит аккаунт и на PostgreSQL.
382
+ - `migrate` работает без права `CREATE` на базу, если схему заранее создал DBA, и с `pg_trgm`,
383
+ установленным в схему вне `search_path` (например, `extensions`).
384
+ - Миграция `0008` заполняет `normalized_ref` одним запросом вместо построчного обновления.
385
+ - Чистка по срокам хранения больше не читает таблицы целиком и не удаляет всё одной
386
+ транзакцией. Доставка вебхука, событие которой уже удалено, больше не захватывается
387
+ диспетчером на каждом круге.
388
+ - Фоновые запросы на большом парке: выбор кандидатов сверки и снятие временных состояний идут
389
+ по индексам, сводка обзора считает аренды и открытые вопросы одним проходом. На 300 000
390
+ аккаунтов выбор кандидатов сверки — 0,01 с вместо 3,9 с, обзор — 0,4–0,9 с вместо 2,6–3,2 с.
391
+ Очередь отключённого вебхук-endpoint'а больше не перебирается при каждом захвате доставок.
392
+ - Повтор операции с тем же `key` после сетевой ошибки больше не выглядит как «уже выполнено»:
393
+ раньше `op.duplicate` был `True` и действие не повторялось никогда.
394
+ - `pool.lease(idempotency_key=...)` из пула другого сервиса больше не возвращает чужую аренду
395
+ вместе с её credentials; тот же ключ в другом пространстве не даёт `IntegrityError`.
396
+ - Ошибки драйвера БД больше не выходят из библиотеки: lock/statement timeout, deadlock и конфликт
397
+ сериализации дают `DatabaseBusy`, остальное — `StorageError`. `token` аренды и параметры
398
+ запросов не попадают в тексты исключений, предупреждения heartbeat/release и отчёт импорта.
399
+ - SSRF через вебхуки: URL с IPv4 в десятичной, шестнадцатеричной, восьмеричной и сокращённой
400
+ записи (`2130706433`, `0x7f000001`, `0177.0.0.1`, `127.1`) отклоняется; имя резолвится перед
401
+ соединением, каждый адрес проверяется, соединение идёт с проверенным IP (DNS rebinding).
402
+ - Токен бота Telegram и путь URL вебхука больше не попадают в лог `httpx`; у
403
+ `TelegramUnavailable` и ошибки `webhooks.send_test` нет исключения `httpx` в цепочке.
404
+ - Процессы `identity-hub maintenance` и `identity-hub dispatcher` больше не завершаются от
405
+ ошибки базы на очередном тике: ошибка логируется, повтор идёт с паузой от 1 до 60 секунд.
406
+ - Диспетчер больше не перечитывает события целых суток на каждую пачку: пересчитываются
407
+ только затронутые минутные корзины. `rollups --rebuild` не стирает телеметрию операций.
408
+ Процесс без dispatcher не копит буфер метрик бесконечно.
409
+ - Override верификации в `verified` теперь даёт допуск: `verified_factors` согласуется.
410
+ `Override("standing", "typo", "banned")` больше не банит аккаунт мимо строки региона, а
411
+ override `auth` без ключа обновляет `auth_state`.
412
+ - Одна запись больше не роняет импорт: негодный URL прокси давал сырой `ValueError` с фрагментом
413
+ пароля, обрывал `imports.run` без отчёта, а в фоновом режиме навсегда блокировал очередь
414
+ импорта пространства и задерживал остальные пространства.
415
+ - Фоновый импорт с `update_session` больше не показывает обновлённые записи как `failed`.
416
+ - Импорт больше не привязывает к прокси аккаунты сверх `max_identities`.
417
+ - Параллельные `proxies.report_failure` больше не теряют приращения `error_score`: сложение
418
+ выполняется в хранилище одной командой.
419
+ - Простой провайдера прокси или контрольного адреса чекера больше не переводит всю группу в
420
+ `dead`: исключение чекера и одновременный отказ всех прокси группы отказом прокси не считаются.
421
+ - Обслуживание больше не останавливается из-за одного аккаунта: строки с `until` без перехода
422
+ не заслоняют очередь таймеров, аккаунт незарегистрированного сервиса пропускается с метрикой,
423
+ сбой одной группы задач не мешает остальным.
424
+ - `sync.refresh_many` больше не тратит весь лимит на `provisioning`-аккаунты и на аккаунты со
425
+ стабильно падающей пробой; `refresh_many(["verification"])` для сервиса без факторов не падает
426
+ с `IndexError`; `MemoryStore` больше не отдаёт `retired`-аккаунты на сверку.
427
+ - Вход больше не застревает в `logging_in`: сессия не по схеме, сбой записи и отмена задачи
428
+ возвращают `auth` в состояние до входа. Вход дольше 40 с больше не теряет сессию — аренда
429
+ продлевается heartbeat'ом.
430
+ - Массовое событие больше не теряет уведомления Telegram из-за лимита Bot API, большой
431
+ дайджест не умирает целиком с HTTP 400.
432
+ - Повтор пачки событий диспетчером больше не дублирует уведомления без дайджеста: уникальность
433
+ `(rule_id, событие)`, миграция `0012` (накопленные дубли удаляются).
434
+ - `MemoryStore`: `sent_at` уведомления, `NotFound` для правила с несуществующим каналом,
435
+ доставки отключённого канала не заслоняют живые.
436
+ - Медленный получатель вебхука больше не блокирует диспетчер: доставка идёт параллельно, а
437
+ пачка, не уложившаяся в срок захвата, не доставляется повторно другим экземпляром.
438
+ - Результат доставки больше не стирает ручную причину отключения endpoint'а; в `MemoryStore`
439
+ доставки отключённого endpoint'а не заслоняют живые.
440
+ - «Ядовитое» событие больше не останавливает подписчика навсегда: курсор колбэка сдвигается
441
+ по одному событию, обработанные события не повторяются, после лимита попыток событие
442
+ пропускается с записью в журнал.
443
+ - Параллельные изменения одной identity (`add_tags`, слияние меток и профиля, заметка,
444
+ требования, `verified_factors`) больше не затирают друг друга: строка блокируется до чтения.
445
+ - `pool.lease(wait=...)` и `space.sync.refresh()` под `FakeClock` больше не зависают; сдвиг
446
+ `FakeClock` внутри аренды больше не даёт ложный `LeaseLost("heartbeat")`.
447
+ - Чистильщик истёкших аренд отбирает аренды с открытой историей (миграция `0011`, индекс
448
+ `ix_ih_lease_history_open_space`): раньше более 500 давно освобождённых аренд заслоняли
449
+ брошенную, и она оставалась открытой навсегда.
450
+ - Удаление аккаунта закрывает историю его истёкшей, но ещё не обработанной аренды.
451
+ - Один неудачный heartbeat больше не отменяет аренду: продление повторяется с коротким backoff
452
+ в пределах дедлайна `ttl − safety_margin`.
453
+ - Повторная отмена задачи во время освобождения больше не оставляет аренду висеть до TTL:
454
+ запись идёт под `asyncio.shield` с таймаутом, а неудавшееся освобождение можно повторить.
455
+ - `rotate-key` больше не завершается «успешно», оставив строки под старым ключом (пропуск
456
+ заблокированных строк и выход по неполной пачке); одна нечитаемая строка не обрывает пачку.
457
+ - Фоновый импорт после удаления старого ключа: прогон завершается `failed`, а не `succeeded`,
458
+ и нерасшифрованные записи не удаляются.
459
+ - Ошибка формата мастер-ключа больше не печатает материал ключа (перепутанный порядок
460
+ `base64:key_id`) в исключении и stderr CLI.
461
+ - Deadlock двух параллельных импортов с `on_existing="update_session"` и общими ref.
462
+
463
+ ### Removed
464
+
465
+ - **Несовместимо:** 84 плоских метода `SpaceHandle` (`register`, `search`, `identity_card`,
466
+ `import_many`, `reveal_secret`, `add_proxy`, `pool`, `proxy_health` и другие) удалены без
467
+ периода совместимости: API ещё не публиковался. Вместо них — неймспейсы (`space.identities.ensure`,
468
+ `space.identities.search`, `space.secrets.reveal`, `space.proxies.ensure`, `space.pools.define`,
469
+ `space.proxies.health`…), см. [«Карта API»](docs/guide/api.md). У `SpaceHandle` публичны только
470
+ неймспейсы, `using` и `ref`.
471
+ - `ProxyPolicy(pool=...)` — используйте `ProxyPolicy(group=...)`; `Pool.check_capacity()` —
472
+ `Pool.capacity()`. Таблица миграции `docs/api-migration.md` и `identity_hub.api.warn_deprecated`
473
+ удалены.
474
+
475
+ ## Внутренний срез 1.0.0a1 - 2026-09-30
476
+
477
+ Не публиковался: под этим номером версии на PyPI вышел раздел выше.
478
+
479
+ ### Added
480
+
481
+ - Полное руководство по установке, быстрому старту, CLI и production-интеграции.
482
+ - Исполняемый Telegram-подобный пример на `MemoryStore`.
483
+ - Руководство `docs/guide`: сервисы, аккаунты, машины состояний, вход, верификация,
484
+ аренды, лимиты, прокси, события, тестирование, рецепты и ограничения альфа-версии;
485
+ диаграммы архитектуры и состояний; исполняемый сценарий `examples/account_lifecycle.py`.
486
+
487
+ ### Fixed
488
+
489
+ - `LeaseMode.SHARED`: параллельные shared-аренды одного аккаунта больше не получают
490
+ `StaleFencing` — shared-захват не увеличивает fencing; в PostgreSQL shared-аккаунты
491
+ выдаются по давности последнего захвата, а не всегда один и тот же; `idempotency_key`
492
+ для shared-пула даёт `ValidationFailed` вместо молчаливого игнорирования (D-140).
493
+ - `identity-hub rollups --purge-minutes` ничего не делал из-за недостижимого кода.
494
+ - Стабильная корневая поверхность импортов для сценария публичного API.
495
+ - Матрица трассировки всех требований FR/G/T/S на исполняемые тесты.
496
+ - Публичные read-модели (overview, аренды, прокси, события, доставки, очередь
497
+ ручных действий), аналитические rollups и почасовые снимки состояния;
498
+ миграции `0005`–`0007`.
499
+
500
+ ### Changed
501
+
502
+ - Extra `telegram` больше не тянет `aiogram`: уведомления отправляются прямым
503
+ вызовом Bot API `sendMessage` через `httpx` (`HttpTelegramTransport`).
504
+ Протокол `TelegramTransport` не изменился.
505
+ - Ответы Bot API 400/401/403/404 (`TelegramRejected`) сразу переводят уведомление
506
+ в `dead` без исчерпания ретраев; сеть и 5xx (`TelegramUnavailable`) повторяются.
507
+ - Sdist содержит только библиотеку, тесты, примеры и пользовательскую документацию;
508
+ веб-админка в пакет не входит.
509
+
510
+ ## [0.1.0.dev0] - 2026-09-27
511
+
512
+ ### Added
513
+
514
+ - Декларативные сервисы, пространства, аккаунты, регионы состояний и связи.
515
+ - Конкурентно безопасные аренды с heartbeat, fencing, квотами и pacing.
516
+ - Зашифрованные credentials, сессии, прокси и управляемая ротация KEK.
517
+ - PostgreSQL-хранилище, встроенные миграции Alembic и отдельные схемы.
518
+ - Прокси, вход, challenge, верификации, пробы, импорт и полнотекстовый поиск.
519
+ - Outbox, dispatcher, вебхуки, Telegram-уведомления, maintenance и метрики.
520
+ - CLI `migrate`, `dispatcher`, `maintenance`, `doctor` и `rotate-key`.
521
+ - `MemoryStore`, pytest-плагин, фабрики и общий conformance-набор для адаптеров.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Identity Hub contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.