supertable 2.0.5__tar.gz → 2.0.7__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 (168) hide show
  1. {supertable-2.0.5/supertable.egg-info → supertable-2.0.7}/PKG-INFO +1 -1
  2. {supertable-2.0.5 → supertable-2.0.7}/pyproject.toml +1 -1
  3. {supertable-2.0.5 → supertable-2.0.7}/setup.py +1 -1
  4. {supertable-2.0.5 → supertable-2.0.7}/supertable/__init__.py +1 -1
  5. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/admin.py +1 -1
  6. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/logger.py +2 -2
  7. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/retention.py +3 -7
  8. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/tests/test_retention.py +7 -4
  9. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/writer_redis.py +1 -1
  10. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/settings.py +1 -1
  11. {supertable-2.0.5 → supertable-2.0.7}/supertable/data_writer.py +5 -1
  12. {supertable-2.0.5 → supertable-2.0.7}/supertable/meta_reader.py +5 -2
  13. {supertable-2.0.5 → supertable-2.0.7}/supertable/mirroring/mirror_formats.py +1 -1
  14. {supertable-2.0.5 → supertable-2.0.7}/supertable/monitoring_writer.py +30 -19
  15. {supertable-2.0.5 → supertable-2.0.7}/supertable/plan_extender.py +4 -2
  16. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/tests/test_rbac.py +1 -1
  17. {supertable-2.0.5 → supertable-2.0.7}/supertable/redis_catalog.py +17 -39
  18. {supertable-2.0.5 → supertable-2.0.7}/supertable/redis_infra.py +1 -1
  19. supertable-2.0.7/supertable/redis_keys.py +810 -0
  20. {supertable-2.0.5 → supertable-2.0.7}/supertable/staging_area.py +2 -2
  21. {supertable-2.0.5 → supertable-2.0.7}/supertable/super_table.py +6 -4
  22. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_data_writer.py +4 -2
  23. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_dedup_on_read_write.py +3 -3
  24. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_meta_reader.py +44 -44
  25. supertable-2.0.7/supertable/tests/test_redis_key_prefix.py +439 -0
  26. {supertable-2.0.5 → supertable-2.0.7/supertable.egg-info}/PKG-INFO +1 -1
  27. supertable-2.0.5/supertable/redis_keys.py +0 -404
  28. supertable-2.0.5/supertable/tests/test_redis_key_prefix.py +0 -236
  29. {supertable-2.0.5 → supertable-2.0.7}/LICENSE +0 -0
  30. {supertable-2.0.5 → supertable-2.0.7}/README.md +0 -0
  31. {supertable-2.0.5 → supertable-2.0.7}/requirements.txt +0 -0
  32. {supertable-2.0.5 → supertable-2.0.7}/setup.cfg +0 -0
  33. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/__init__.py +0 -0
  34. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/chain.py +0 -0
  35. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/consumers.py +0 -0
  36. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/crypto.py +0 -0
  37. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/events.py +0 -0
  38. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/export.py +0 -0
  39. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/middleware.py +0 -0
  40. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/reader.py +0 -0
  41. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/tests/__init__.py +0 -0
  42. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/tests/test_chain.py +0 -0
  43. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/tests/test_crypto.py +0 -0
  44. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/tests/test_emit.py +0 -0
  45. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/tests/test_events.py +0 -0
  46. {supertable-2.0.5 → supertable-2.0.7}/supertable/audit/writer_parquet.py +0 -0
  47. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/__init__.py +0 -0
  48. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/defaults.py +0 -0
  49. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/homedir.py +0 -0
  50. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/tests/__init__.py +0 -0
  51. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/tests/test_defaults.py +0 -0
  52. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/tests/test_homedir.py +0 -0
  53. {supertable-2.0.5 → supertable-2.0.7}/supertable/config/tests/test_settings.py +0 -0
  54. {supertable-2.0.5 → supertable-2.0.7}/supertable/data_classes.py +0 -0
  55. {supertable-2.0.5 → supertable-2.0.7}/supertable/data_reader.py +0 -0
  56. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/__init__.py +0 -0
  57. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/__init__.py +0 -0
  58. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/__main__.py +0 -0
  59. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/check_filter_builder.py +0 -0
  60. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/controller.py +0 -0
  61. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/data_writer_helpers.py +0 -0
  62. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/defaults.py +0 -0
  63. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/dummy_data.py +0 -0
  64. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/read_parquet_header.py +0 -0
  65. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s01_01_01_create_super_table.py +0 -0
  66. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s01_01_02_enable_mirroring_formats.py +0 -0
  67. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s01_02_create_roles.py +0 -0
  68. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s01_03_create_users.py +0 -0
  69. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_01_write_dummy_data.py +0 -0
  70. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_02_write_single_data.py +0 -0
  71. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_03_01_write_staging.py +0 -0
  72. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_03_02_create_pipe.py +0 -0
  73. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_04_01_write_monitoring_simple.py +0 -0
  74. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_04_02_write_monitoring_parallel.py +0 -0
  75. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s02_05_write_tombstone.py +0 -0
  76. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_01_read_data_error.py +0 -0
  77. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_02_01_read_super_data_ok.py +0 -0
  78. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_02_02_read_table_data_ok.py +0 -0
  79. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_03_read_meta.py +0 -0
  80. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_04_read_staging.py +0 -0
  81. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_06_01_read_roles.py +0 -0
  82. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_06_02_read_user.py +0 -0
  83. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_07_01_estimate_read.py +0 -0
  84. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_07_02_estimate_files.py +0 -0
  85. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s03_08_read_snapshot_history.py +0 -0
  86. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s04_01_03_delete_pipe.py +0 -0
  87. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s05_01_delete_table.py +0 -0
  88. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/quickstart/s05_02_delete_super_table.py +0 -0
  89. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/webshop/__init__.py +0 -0
  90. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/webshop/core.py +0 -0
  91. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/webshop/defaults.py +0 -0
  92. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/webshop/generate.py +0 -0
  93. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/webshop/load.py +0 -0
  94. {supertable-2.0.5 → supertable-2.0.7}/supertable/demo/webshop/topup.py +0 -0
  95. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/__init__.py +0 -0
  96. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/data_estimator.py +0 -0
  97. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/duckdb_lite.py +0 -0
  98. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/duckdb_pro.py +0 -0
  99. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/engine_common.py +0 -0
  100. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/engine_enum.py +0 -0
  101. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/executor.py +0 -0
  102. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/plan_stats.py +0 -0
  103. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/spark_thrift.py +0 -0
  104. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/tests/__init__.py +0 -0
  105. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/tests/conftest.py +0 -0
  106. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/tests/test_dedup_read.py +0 -0
  107. {supertable-2.0.5 → supertable-2.0.7}/supertable/engine/tests/test_engine.py +0 -0
  108. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/__init__.py +0 -0
  109. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/benchmarks/__init__.py +0 -0
  110. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/benchmarks/benchmark_locking.py +0 -0
  111. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/benchmarks/measure_lock_speed.py +0 -0
  112. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/benchmarks/measure_lock_time.py +0 -0
  113. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/file_lock.py +0 -0
  114. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/redis_lock.py +0 -0
  115. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/tests/__init__.py +0 -0
  116. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/tests/test_file_lock.py +0 -0
  117. {supertable-2.0.5 → supertable-2.0.7}/supertable/locking/tests/test_redis_lock.py +0 -0
  118. {supertable-2.0.5 → supertable-2.0.7}/supertable/logging.py +0 -0
  119. {supertable-2.0.5 → supertable-2.0.7}/supertable/mirroring/__init__.py +0 -0
  120. {supertable-2.0.5 → supertable-2.0.7}/supertable/mirroring/mirror_delta.py +0 -0
  121. {supertable-2.0.5 → supertable-2.0.7}/supertable/mirroring/mirror_iceberg.py +0 -0
  122. {supertable-2.0.5 → supertable-2.0.7}/supertable/mirroring/mirror_parquet.py +0 -0
  123. {supertable-2.0.5 → supertable-2.0.7}/supertable/processing.py +0 -0
  124. {supertable-2.0.5 → supertable-2.0.7}/supertable/query_plan_manager.py +0 -0
  125. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/__init__.py +0 -0
  126. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/access_control.py +0 -0
  127. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/filter_builder.py +0 -0
  128. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/permissions.py +0 -0
  129. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/role_manager.py +0 -0
  130. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/row_column_security.py +0 -0
  131. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/tests/test_filter_builder.py +0 -0
  132. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/tests/test_rbac_per_table.py +0 -0
  133. {supertable-2.0.5 → supertable-2.0.7}/supertable/rbac/user_manager.py +0 -0
  134. {supertable-2.0.5 → supertable-2.0.7}/supertable/redis_connector.py +0 -0
  135. {supertable-2.0.5 → supertable-2.0.7}/supertable/simple_table.py +0 -0
  136. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/__init__.py +0 -0
  137. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/azure_storage.py +0 -0
  138. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/gcp_storage.py +0 -0
  139. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/local_storage.py +0 -0
  140. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/minio_storage.py +0 -0
  141. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/s3_storage.py +0 -0
  142. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/storage_factory.py +0 -0
  143. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/storage_interface.py +0 -0
  144. {supertable-2.0.5 → supertable-2.0.7}/supertable/storage/tests/test_storage.py +0 -0
  145. {supertable-2.0.5 → supertable-2.0.7}/supertable/super_pipe.py +0 -0
  146. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/__init__.py +0 -0
  147. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_align_to_schema_fix.py +0 -0
  148. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_data_reader.py +0 -0
  149. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_data_writer_comprehensive.py +0 -0
  150. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_data_writer_tombstones.py +0 -0
  151. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_newer_than.py +0 -0
  152. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_process_delete_only.py +0 -0
  153. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_processing.py +0 -0
  154. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_query_sql.py +0 -0
  155. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_simple_table.py +0 -0
  156. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_small_file_compaction.py +0 -0
  157. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_super_table.py +0 -0
  158. {supertable-2.0.5 → supertable-2.0.7}/supertable/tests/test_supertable_all.py +0 -0
  159. {supertable-2.0.5 → supertable-2.0.7}/supertable/utils/__init__.py +0 -0
  160. {supertable-2.0.5 → supertable-2.0.7}/supertable/utils/helper.py +0 -0
  161. {supertable-2.0.5 → supertable-2.0.7}/supertable/utils/sql_parser.py +0 -0
  162. {supertable-2.0.5 → supertable-2.0.7}/supertable/utils/tests/test_sql_parser_columns.py +0 -0
  163. {supertable-2.0.5 → supertable-2.0.7}/supertable/utils/timer.py +0 -0
  164. {supertable-2.0.5 → supertable-2.0.7}/supertable.egg-info/SOURCES.txt +0 -0
  165. {supertable-2.0.5 → supertable-2.0.7}/supertable.egg-info/dependency_links.txt +0 -0
  166. {supertable-2.0.5 → supertable-2.0.7}/supertable.egg-info/entry_points.txt +0 -0
  167. {supertable-2.0.5 → supertable-2.0.7}/supertable.egg-info/requires.txt +0 -0
  168. {supertable-2.0.5 → supertable-2.0.7}/supertable.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supertable
3
- Version: 2.0.5
3
+ Version: 2.0.7
4
4
  Summary: SuperTable — versioned data lake library for SQL analytics on Parquet + Redis.
5
5
  Author: Levente Kupas
6
6
  Author-email: Levente Kupas <lkupas@kladnasoft.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "supertable"
7
- version = "2.0.5"
7
+ version = "2.0.7"
8
8
  description = "SuperTable — versioned data lake library for SQL analytics on Parquet + Redis."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -19,7 +19,7 @@ long_description = readme.read_text(encoding="utf-8") if readme.exists() else ""
19
19
 
20
20
  setup(
21
21
  name="supertable",
22
- version="2.0.5",
22
+ version="2.0.7",
23
23
  description="SuperTable — versioned data lake library for SQL analytics on Parquet + Redis.",
24
24
  long_description=long_description,
25
25
  long_description_content_type="text/markdown",
@@ -25,7 +25,7 @@ See the ``supertable.demo`` package for runnable end-to-end demos and the
25
25
  project documentation for the full API surface.
26
26
  """
27
27
 
28
- __version__ = "2.0.5"
28
+ __version__ = "2.0.7"
29
29
 
30
30
  # Re-export the core public surface so users can do ``from supertable import …``
31
31
  # instead of remembering submodule paths.
@@ -9,7 +9,7 @@ Persistence
9
9
  -----------
10
10
  Stored in Redis as a HASH at:
11
11
 
12
- supertable:{org}:audit:config
12
+ supertable:{org}:system:audit:config
13
13
 
14
14
  Fields
15
15
  ------
@@ -408,7 +408,7 @@ _LOGGERS: Dict[str, "AuditLogger | NullAuditLogger"] = {}
408
408
  _LOGGERS_LOCK = threading.Lock()
409
409
 
410
410
  # Per-org config cache: org → (config, expires_at_seconds).
411
- # Resolved against env defaults + Redis override (supertable:{org}:audit:config).
411
+ # Resolved against env defaults + Redis override (supertable:{org}:system:audit:config).
412
412
  _ORG_CFG_CACHE: Dict[str, "tuple[AuditConfig, float]"] = {}
413
413
  _ORG_CFG_TTL_S: float = 30.0 # toggle takes effect within this many seconds
414
414
 
@@ -416,7 +416,7 @@ _ORG_CFG_TTL_S: float = 30.0 # toggle takes effect within this many seconds
416
416
  def _resolve_config_for(organization: str) -> AuditConfig:
417
417
  """Resolve the effective AuditConfig for *organization*.
418
418
 
419
- Merges the Redis override at ``supertable:{org}:audit:config`` over the
419
+ Merges the Redis override at ``supertable:{org}:system:audit:config`` over the
420
420
  env-var defaults. Cached for _ORG_CFG_TTL_S seconds.
421
421
  """
422
422
  now = time.time()
@@ -25,17 +25,13 @@ import re
25
25
  from datetime import datetime, timedelta, timezone
26
26
  from typing import Any, Dict, List, Optional
27
27
 
28
- logger = logging.getLogger(__name__)
28
+ from supertable import redis_keys as RK
29
29
 
30
- # ---------------------------------------------------------------------------
31
- # Redis key for legal hold runtime override
32
- # ---------------------------------------------------------------------------
33
-
34
- _LEGAL_HOLD_KEY_TEMPLATE = "supertable:{org}:audit:legal_hold"
30
+ logger = logging.getLogger(__name__)
35
31
 
36
32
 
37
33
  def _legal_hold_key(org: str) -> str:
38
- return _LEGAL_HOLD_KEY_TEMPLATE.replace("{org}", org)
34
+ return RK.audit_legal_hold(org)
39
35
 
40
36
 
41
37
  # ---------------------------------------------------------------------------
@@ -28,10 +28,13 @@ from supertable.audit import retention
28
28
 
29
29
  class TestLegalHoldKey:
30
30
  def test_template_substitution(self) -> None:
31
- assert retention._legal_hold_key("acme") == "supertable:acme:audit:legal_hold"
31
+ assert retention._legal_hold_key("acme") == "supertable:acme:system:audit:legal_hold"
32
32
 
33
- def test_handles_special_characters(self) -> None:
34
- assert retention._legal_hold_key("acme-EU") == "supertable:acme-EU:audit:legal_hold"
33
+ def test_handles_hyphens(self) -> None:
34
+ # v2 _safe() requires the org-segment regex
35
+ # ^[a-z0-9][a-z0-9_-]{0,63}$ — uppercase letters are rejected,
36
+ # hyphens + lowercase alphanumerics are allowed.
37
+ assert retention._legal_hold_key("acme-eu") == "supertable:acme-eu:system:audit:legal_hold"
35
38
 
36
39
 
37
40
  class TestParsePartitionDate:
@@ -171,7 +174,7 @@ class TestSetLegalHold:
171
174
  assert result == {"ok": True, "legal_hold": True, "organization": "acme"}
172
175
 
173
176
  fake_client.set.assert_called_once_with(
174
- "supertable:acme:audit:legal_hold", "1"
177
+ "supertable:acme:system:audit:legal_hold", "1"
175
178
  )
176
179
  assert audit_calls and audit_calls[0]["organization"] == "acme"
177
180
 
@@ -6,7 +6,7 @@ Provides real-time queryability (XRANGE), consumer groups for external
6
6
  SIEM tools (Splunk, Sentinel, ELK), and TTL-based eviction.
7
7
 
8
8
  Each organization gets its own stream:
9
- supertable:{org}:audit:stream
9
+ supertable:{org}:system:audit:stream
10
10
 
11
11
  The internal archival consumer group ("__archival__") is created
12
12
  automatically. External SIEM consumer groups are managed via the
@@ -261,7 +261,7 @@ class Settings:
261
261
 
262
262
  # ── Audit ────────────────────────────────────────────────────────
263
263
  # Audit is OFF by default. Enable per-organization in the WebUI
264
- # /ui/audit → Compliance tab (persisted at supertable:{org}:audit:config),
264
+ # /ui/audit → Compliance tab (persisted at supertable:{org}:system:audit:config),
265
265
  # or globally via the SUPERTABLE_AUDIT_ENABLED env var.
266
266
  SUPERTABLE_AUDIT_ENABLED: bool = False # SUPERTABLE_AUDIT_ENABLED
267
267
  SUPERTABLE_AUDIT_RETENTION_DAYS: int = 2555 # SUPERTABLE_AUDIT_RETENTION_DAYS (~7 years)
@@ -535,8 +535,12 @@ class DataWriter:
535
535
  # guaranteed to reach Redis before this scope closes.
536
536
  try:
537
537
  if stats_payload is not None:
538
+ # Monitoring is org-wide as of SDK 2.2.0 — record the
539
+ # touched supertable in the payload's ``supertables``
540
+ # field. A DataWriter only touches one supertable, but
541
+ # the list shape is uniform with cross-sup events.
542
+ stats_payload["supertables"] = [self.super_table.super_name]
538
543
  with MonitoringWriter(
539
- super_name=self.super_table.super_name,
540
544
  organization=self.super_table.organization,
541
545
  monitor_type="writes",
542
546
  ) as monitor:
@@ -154,7 +154,7 @@ class MetaReader:
154
154
  count=1000,
155
155
  ):
156
156
  key_str = key if isinstance(key, str) else key.decode('utf-8')
157
- table_name = key_str.rsplit("meta:leaf:", 1)[-1]
157
+ table_name = key_str.rsplit("meta:leaf:doc:", 1)[-1]
158
158
  if table_name and table_name not in seen:
159
159
  seen.add(table_name)
160
160
  tables.append(table_name)
@@ -507,7 +507,10 @@ def list_supers(organization: str, role_name: str) -> List[str]:
507
507
 
508
508
  items = _get_redis_items(pattern)
509
509
  for item in items:
510
- super_name = item.split(':')[2]
510
+ parsed = RK.parse_lake_key(item)
511
+ if parsed is None:
512
+ continue
513
+ _, super_name = parsed
511
514
  try:
512
515
  check_meta_access(
513
516
  super_name=super_name,
@@ -40,7 +40,7 @@ class MirrorFormats:
40
40
  Redis-backed format mirror configuration and dispatch.
41
41
 
42
42
  Storage:
43
- - Redis key: supertable:{org}:{super}:meta:mirrors
43
+ - Redis key: supertable:{org}:lakes:{super}:meta:mirrors
44
44
  value: {"formats": ["DELTA", ...], "ts": <epoch_ms>}
45
45
  """
46
46
 
@@ -109,19 +109,21 @@ class NullMonitoringLogger:
109
109
  @dataclass(frozen=True)
110
110
  class _MonitorKey:
111
111
  organization: str
112
- super_name: str
113
112
  monitor_type: str
114
113
 
115
114
  @property
116
115
  def path_key(self) -> str:
117
- # Matches your log style: "org/super/monitor_type"
118
- return f"{self.organization}/{self.super_name}/{self.monitor_type}"
116
+ # Matches the log style: "org/monitor_type"
117
+ return f"{self.organization}/{self.monitor_type}"
119
118
 
120
119
  @property
121
120
  def redis_list_key(self) -> str:
122
- # Namespaced under the supertable scope:
123
- # supertable:{org}:{sup}:monitor:{monitor_type}
124
- return RK.monitor(self.organization, self.super_name, self.monitor_type)
121
+ # v2.2: org-wide monitoring lives at position 2.
122
+ # supertable:{org}:monitor:{monitor_type}
123
+ # Cross-supertable queries record exactly one canonical entry
124
+ # here; attribution is preserved in the payload's
125
+ # ``supertables: [str]`` field.
126
+ return RK.monitor(self.organization, self.monitor_type)
125
127
 
126
128
 
127
129
  class _AsyncMonitoringLogger:
@@ -455,7 +457,6 @@ def _monitoring_enabled() -> bool:
455
457
 
456
458
  def get_monitoring_logger(
457
459
  *,
458
- super_name: str,
459
460
  organization: str,
460
461
  monitor_type: str = "plans",
461
462
  redis_connector: Optional["RedisConnector"] = None,
@@ -464,11 +465,16 @@ def get_monitoring_logger(
464
465
  Return a monitoring logger (context manager + log_metric + request_flush + debug fields).
465
466
 
466
467
  This function never raises; on any failure it returns a NullMonitoringLogger.
468
+
469
+ Per-supertable attribution is encoded **in the payload**, not in
470
+ the Redis key — callers include a ``supertables: [str]`` field in
471
+ each payload passed to ``log_metric``. The org-level shape lets
472
+ cross-supertable queries record a single canonical entry.
467
473
  """
468
474
  if not _monitoring_enabled():
469
475
  return NullMonitoringLogger()
470
476
 
471
- key = _MonitorKey(organization=organization, super_name=super_name, monitor_type=monitor_type)
477
+ key = _MonitorKey(organization=organization, monitor_type=monitor_type)
472
478
  cache_key = key.path_key
473
479
 
474
480
  try:
@@ -490,33 +496,38 @@ def get_monitoring_logger(
490
496
 
491
497
  class MonitoringWriter:
492
498
  """
493
- Backwards-compatible facade.
499
+ Monitoring façade — org-level writer (SDK 2.2.0+).
494
500
 
495
- Supported usage patterns:
496
- mon = MonitoringWriter(...)
497
- mon.log_metric({...})
501
+ Usage:
502
+ mon = MonitoringWriter(organization=org, monitor_type="plans")
503
+ mon.log_metric({"supertables": ["sales", "customers"], ...})
498
504
  mon.request_flush()
499
505
 
500
- And context-manager style:
501
- with MonitoringWriter(...) as mon:
502
- mon.log_metric({...})
506
+ Context-manager style:
507
+ with MonitoringWriter(organization=org, monitor_type="writes") as mon:
508
+ mon.log_metric({"supertables": ["sales"], ...})
509
+
510
+ Per-supertable attribution: the Redis key is org-level
511
+ (``supertable:{org}:monitor:{monitor_type}``). Callers include a
512
+ ``supertables: [str]`` field in each payload — the full list of
513
+ supertables the event touches. Cross-supertable queries (JOINs,
514
+ multi-target writes) pass the full list. Events not tied to any
515
+ supertable pass ``supertables: []`` (or omit it — the writer
516
+ will default to an empty list).
503
517
 
504
- It also forwards debug attributes used in examples (queue_stats, etc.).
518
+ Also forwards debug attributes used in examples (queue_stats, etc.).
505
519
  """
506
520
 
507
521
  def __init__(
508
522
  self,
509
523
  *,
510
- super_name: str,
511
524
  organization: str,
512
525
  monitor_type: str = "plans",
513
526
  redis_connector: Optional["RedisConnector"] = None,
514
527
  ):
515
- self.super_name = super_name
516
528
  self.organization = organization
517
529
  self.monitor_type = monitor_type
518
530
  self._logger: MonitoringLogger = get_monitoring_logger(
519
- super_name=super_name,
520
531
  organization=organization,
521
532
  monitor_type=monitor_type,
522
533
  redis_connector=redis_connector,
@@ -123,10 +123,12 @@ def extend_execution_plan(
123
123
  logger.error("Failed to build monitoring stats payload: %s", e)
124
124
  return # nothing else to do safely
125
125
 
126
- # Log the metric (buffered; background writer flushes)
126
+ # Log the metric (buffered; background writer flushes).
127
+ # Monitoring is org-wide as of SDK 2.2.0 — the touched supertable
128
+ # is recorded in the payload's ``supertables: [str]`` field.
127
129
  try:
130
+ stats["supertables"] = [query_plan_manager.super_name]
128
131
  with MonitoringWriter(
129
- super_name=query_plan_manager.super_name,
130
132
  organization=query_plan_manager.organization,
131
133
  monitor_type="plans",
132
134
  ) as monitor:
@@ -1659,7 +1659,7 @@ class TestKeyNamespace(unittest.TestCase):
1659
1659
  RK.rbac_role_type_index("o", "s", "admin"),
1660
1660
  ]
1661
1661
  for k in keys:
1662
- self.assertTrue(k.startswith("supertable:o:s:rbac:"), f"Bad key: {k}")
1662
+ self.assertTrue(k.startswith("supertable:o:lakes:s:rbac:"), f"Bad key: {k}")
1663
1663
 
1664
1664
  def test_rbac_keys_do_not_collide(self):
1665
1665
  """All 8 key patterns for same org/sup produce distinct keys."""
@@ -117,7 +117,7 @@ return v
117
117
  # ARGV[1] role_id
118
118
  # ARGV[2] now_ms (string)
119
119
  # ARGV[3] role_name_lower (or "")
120
- # ARGV[4] user_doc_key_prefix (e.g. "supertable:{org}:{sup}:rbac:users:doc:")
120
+ # ARGV[4] user_doc_key_prefix (e.g. "supertable:{org}:lakes:{sup}:rbac:users:doc:")
121
121
  # KEYS layout:
122
122
  # KEYS[1] role_doc_key
123
123
  # KEYS[2] role_index_key
@@ -264,7 +264,7 @@ return 1
264
264
  timeout_s: int = 30,
265
265
  ) -> Optional[str]:
266
266
  """Acquire lock for staging/pipe operations:
267
- supertable:{org}:{sup}:lock:stage:{stage_name}
267
+ supertable:{org}:lakes:{sup}:lock:stage:doc:{stage_name}
268
268
  """
269
269
  return self._locker.acquire(RK.lock_stage(org, sup, stage_name), ttl_s=ttl_s, timeout_s=timeout_s)
270
270
 
@@ -343,10 +343,10 @@ return 1
343
343
  cursor, keys = self.r.scan(cursor=cursor, match=pattern, count=500)
344
344
  for k in keys:
345
345
  ks = k if isinstance(k, str) else k.decode("utf-8")
346
- parts = ks.split(":")
347
- if len(parts) < 4:
346
+ parsed = RK.parse_lake_key(ks)
347
+ if parsed is None:
348
348
  continue
349
- sup_name = parts[2]
349
+ _, sup_name = parsed
350
350
  if sup_name == source_sup:
351
351
  continue
352
352
  try:
@@ -685,8 +685,8 @@ return 1
685
685
  role_name = self.r.hget(key, "role_name") or ""
686
686
  if isinstance(role_name, bytes):
687
687
  role_name = role_name.decode("utf-8")
688
- # rbac_user_doc(..., "") yields the canonical "...:rbac:users:doc:" prefix.
689
- user_doc_key_prefix = RK.rbac_user_doc(org, sup, "")
688
+ # The Lua script appends each user_id to this prefix.
689
+ user_doc_key_prefix = RK.rbac_user_doc_prefix(org, sup)
690
690
  result = self._rbac_delete_role(
691
691
  keys=[
692
692
  key,
@@ -954,7 +954,7 @@ return 1
954
954
  # ------------- Listings via SCAN -------------
955
955
 
956
956
  def scan_leaf_keys(self, org: str, sup: str, count: int = 1000) -> Iterator[str]:
957
- """Yields full Redis keys: supertable:{org}:{sup}:meta:leaf:* (replica-aware)."""
957
+ """Yields full Redis keys: supertable:{org}:lakes:{sup}:meta:leaf:doc:* (replica-aware)."""
958
958
  info = self._resolve_replica_info(org, sup)
959
959
  effective_sup = info[0] if info else sup
960
960
  allowed = info[1] if info else None
@@ -967,7 +967,7 @@ return 1
967
967
  for k in keys:
968
968
  ks = k if isinstance(k, str) else k.decode('utf-8')
969
969
  if allowed:
970
- simple = ks.rsplit("meta:leaf:", 1)[-1]
970
+ simple = ks.rsplit("meta:leaf:doc:", 1)[-1]
971
971
  if simple not in allowed:
972
972
  continue
973
973
  yield ks
@@ -1003,7 +1003,7 @@ return 1
1003
1003
  continue
1004
1004
  try:
1005
1005
  obj = json.loads(raw)
1006
- simple = k.rsplit("meta:leaf:", 1)[-1]
1006
+ simple = k.rsplit("meta:leaf:doc:", 1)[-1]
1007
1007
  yield {
1008
1008
  "simple": simple,
1009
1009
  "version": int(obj.get("version", -1)),
@@ -1216,44 +1216,22 @@ return 1
1216
1216
  return None
1217
1217
 
1218
1218
  def list_stagings(self, org: str, sup: str, *, count: int = 1000) -> List[str]:
1219
- """List staging names. Prefers the staging index set; falls back to SCAN."""
1219
+ """List staging names from the staging index set."""
1220
1220
  if not (org and sup):
1221
1221
  return []
1222
1222
  try:
1223
- names = list(self.r.smembers(RK.staging_index(org, sup)) or [])
1224
- if names:
1225
- return sorted({(n if isinstance(n, str) else n.decode('utf-8')) for n in names if n})
1223
+ names = self.r.smembers(RK.staging_index(org, sup)) or set()
1226
1224
  except redis.RedisError as e:
1227
1225
  logger.error(f"[redis-catalog] list_stagings smembers error: {e}")
1228
-
1229
- # Fallback (migration/back-compat): scan exact staging meta keys.
1230
- pattern = RK.staging_pattern(org, sup)
1231
- seen = set()
1232
- cursor = 0
1233
- try:
1234
- while True:
1235
- cursor, keys = self.r.scan(cursor=cursor, match=pattern, count=max(1, int(count)))
1236
- for k in keys or []:
1237
- kk = k if isinstance(k, str) else k.decode("utf-8")
1238
- if ":pipe:" in kk:
1239
- continue
1240
- if kk.endswith(":meta"):
1241
- continue
1242
- name = kk.rsplit("meta:staging:", 1)[-1]
1243
- if name:
1244
- seen.add(name)
1245
- if cursor == 0:
1246
- break
1247
- except redis.RedisError as e:
1248
- logger.error(f"[redis-catalog] list_stagings scan error: {e}")
1249
- return sorted(seen)
1226
+ return []
1227
+ return sorted({(n if isinstance(n, str) else n.decode('utf-8')) for n in names if n})
1250
1228
 
1251
1229
  def delete_staging_meta(self, org: str, sup: str, staging_name: str, *, count: int = 1000) -> int:
1252
1230
  """Delete staging meta and *all* related keys under the staging prefix.
1253
1231
 
1254
1232
  This removes the staging from the staging index set, deletes the staging meta key,
1255
1233
  and deletes any keys matching:
1256
- supertable:{org}:{sup}:meta:staging:{staging_name}:*
1234
+ supertable:{org}:lakes:{sup}:meta:staging:doc:{staging_name}:*
1257
1235
  Returns number of keys deleted (best-effort; does not include SREM).
1258
1236
  """
1259
1237
  if not (org and sup and staging_name):
@@ -1393,7 +1371,7 @@ return 1
1393
1371
  return 0
1394
1372
 
1395
1373
  # ========================================================================= #
1396
- # Spark Thrift cluster management (org-scoped: supertable:{org}:spark:thrifts)
1374
+ # Spark Thrift cluster management (org-scoped: supertable:{org}:system:spark:thrifts)
1397
1375
  # ========================================================================= #
1398
1376
 
1399
1377
  def register_spark_cluster(self, org: str, cluster_id: str, config: Dict[str, Any]) -> None:
@@ -1489,7 +1467,7 @@ return 1
1489
1467
  return candidates[0]
1490
1468
 
1491
1469
  # ========================================================================= #
1492
- # Spark Plug management (org-scoped: supertable:{org}:spark:plugs)
1470
+ # Spark Plug management (org-scoped: supertable:{org}:system:spark:plugs)
1493
1471
  # ========================================================================= #
1494
1472
 
1495
1473
  def register_spark_plug(self, org: str, plug_id: str, config: Dict[str, Any]) -> None:
@@ -177,7 +177,7 @@ class _FallbackCatalog:
177
177
  continue
178
178
  try:
179
179
  obj = json.loads(raw if isinstance(raw, str) else raw.decode("utf-8"))
180
- simple = k.rsplit("meta:leaf:", 1)[-1]
180
+ simple = k.rsplit("meta:leaf:doc:", 1)[-1]
181
181
  yield {
182
182
  "simple": simple,
183
183
  "version": int(obj.get("version", -1)),