lumonox 0.2.5__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 (403) hide show
  1. lumonox-0.2.5/.env.example +126 -0
  2. lumonox-0.2.5/.gitignore +73 -0
  3. lumonox-0.2.5/ALERT_DELIVERY_RUNBOOK.md +59 -0
  4. lumonox-0.2.5/PKG-INFO +20 -0
  5. lumonox-0.2.5/README.md +138 -0
  6. lumonox-0.2.5/alembic.ini +39 -0
  7. lumonox-0.2.5/pyproject.toml +39 -0
  8. lumonox-0.2.5/scripts/backfill_events_to_duckdb.py +83 -0
  9. lumonox-0.2.5/scripts/debug_retention_vacuum.py +97 -0
  10. lumonox-0.2.5/scripts/manual_dashboard_test.py +236 -0
  11. lumonox-0.2.5/scripts/package_wheel.sh +24 -0
  12. lumonox-0.2.5/src/lumonox_backend/__init__.py +5 -0
  13. lumonox-0.2.5/src/lumonox_backend/alembic/env.py +61 -0
  14. lumonox-0.2.5/src/lumonox_backend/alembic/script.py.mako +25 -0
  15. lumonox-0.2.5/src/lumonox_backend/alembic/versions/initial.py +732 -0
  16. lumonox-0.2.5/src/lumonox_backend/alerts.py +31 -0
  17. lumonox-0.2.5/src/lumonox_backend/api/__init__.py +3 -0
  18. lumonox-0.2.5/src/lumonox_backend/api/router.py +12 -0
  19. lumonox-0.2.5/src/lumonox_backend/api/routes/__init__.py +1 -0
  20. lumonox-0.2.5/src/lumonox_backend/api/routes/health.py +491 -0
  21. lumonox-0.2.5/src/lumonox_backend/app.py +62 -0
  22. lumonox-0.2.5/src/lumonox_backend/auth/__init__.py +59 -0
  23. lumonox-0.2.5/src/lumonox_backend/auth/api_keys.py +136 -0
  24. lumonox-0.2.5/src/lumonox_backend/auth/dashboard.py +637 -0
  25. lumonox-0.2.5/src/lumonox_backend/auth/dashboard_security.py +104 -0
  26. lumonox-0.2.5/src/lumonox_backend/auth/rbac.py +101 -0
  27. lumonox-0.2.5/src/lumonox_backend/commercial/__init__.py +1 -0
  28. lumonox-0.2.5/src/lumonox_backend/commercial/plan_limits.py +34 -0
  29. lumonox-0.2.5/src/lumonox_backend/config.py +21 -0
  30. lumonox-0.2.5/src/lumonox_backend/core/__init__.py +7 -0
  31. lumonox-0.2.5/src/lumonox_backend/core/config.py +1018 -0
  32. lumonox-0.2.5/src/lumonox_backend/core/config_env.py +92 -0
  33. lumonox-0.2.5/src/lumonox_backend/dashboard/__init__.py +42 -0
  34. lumonox-0.2.5/src/lumonox_backend/dashboard/duckdb_queries.py +630 -0
  35. lumonox-0.2.5/src/lumonox_backend/dashboard/duckdb_query_utils.py +37 -0
  36. lumonox-0.2.5/src/lumonox_backend/dashboard/error_grouping.py +132 -0
  37. lumonox-0.2.5/src/lumonox_backend/dashboard/event_scope.py +13 -0
  38. lumonox-0.2.5/src/lumonox_backend/dashboard/log_query.py +182 -0
  39. lumonox-0.2.5/src/lumonox_backend/dashboard/messages.py +20 -0
  40. lumonox-0.2.5/src/lumonox_backend/dashboard/overview_derived_widgets.py +164 -0
  41. lumonox-0.2.5/src/lumonox_backend/dashboard/params.py +18 -0
  42. lumonox-0.2.5/src/lumonox_backend/dashboard/parsing.py +7 -0
  43. lumonox-0.2.5/src/lumonox_backend/dashboard/payload_limits.py +14 -0
  44. lumonox-0.2.5/src/lumonox_backend/dashboard/query_snapshot_cache.py +300 -0
  45. lumonox-0.2.5/src/lumonox_backend/dashboard/read_rate_limit.py +89 -0
  46. lumonox-0.2.5/src/lumonox_backend/dashboard/repositories/__init__.py +1 -0
  47. lumonox-0.2.5/src/lumonox_backend/dashboard/repositories/project_ui.py +81 -0
  48. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/__init__.py +1 -0
  49. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/alert_routes.py +413 -0
  50. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/auth_routes.py +548 -0
  51. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/bookmark_routes.py +152 -0
  52. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/diagnosis.py +339 -0
  53. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/error_groups.py +411 -0
  54. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/job_events.py +178 -0
  55. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/log_query_routes.py +247 -0
  56. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/oidc_routes.py +270 -0
  57. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/organization_routes.py +284 -0
  58. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/overview.py +659 -0
  59. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/query_bundle.py +602 -0
  60. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/query_explorer.py +148 -0
  61. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/requests_routes.py +217 -0
  62. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/traces.py +285 -0
  63. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/ui_settings.py +402 -0
  64. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/websockets.py +123 -0
  65. lumonox-0.2.5/src/lumonox_backend/dashboard/routes/widgets.py +99 -0
  66. lumonox-0.2.5/src/lumonox_backend/dashboard/serializers.py +84 -0
  67. lumonox-0.2.5/src/lumonox_backend/dashboard/static_export_mount.py +119 -0
  68. lumonox-0.2.5/src/lumonox_backend/dashboard/studio_nav_pages.py +27 -0
  69. lumonox-0.2.5/src/lumonox_backend/dashboard/studio_showcase.py +321 -0
  70. lumonox-0.2.5/src/lumonox_backend/dashboard/time_window.py +59 -0
  71. lumonox-0.2.5/src/lumonox_backend/dashboard/widget_layout.py +143 -0
  72. lumonox-0.2.5/src/lumonox_backend/dashboard/widget_points_selection.py +50 -0
  73. lumonox-0.2.5/src/lumonox_backend/dashboard_static/404/index.html +1 -0
  74. lumonox-0.2.5/src/lumonox_backend/dashboard_static/404.html +1 -0
  75. lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next.__PAGE__.txt +9 -0
  76. lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._full.txt +20 -0
  77. lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._head.txt +6 -0
  78. lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._index.txt +7 -0
  79. lumonox-0.2.5/src/lumonox_backend/dashboard_static/__next._tree.txt +2 -0
  80. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0-27aiizw1ccj.js +1 -0
  81. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0-ofl9r~na2aa.js +1 -0
  82. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0._d-i80mn1in.js +1 -0
  83. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/00-csyj9a6t-x.js +1 -0
  84. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/00eadyl74e5fh.js +1 -0
  85. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/00ywsx1nh925h.js +1 -0
  86. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/019m0vkkx-5er.js +1 -0
  87. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/01qk1etdnj3x8.js +1 -0
  88. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/01xlw8hd842-c.js +1 -0
  89. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/02s99uve0dntn.js +1 -0
  90. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/03k~wtmusmela.js +1 -0
  91. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/03~g8xwvee~1x.js +1 -0
  92. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/03~yq9q893hmn.js +1 -0
  93. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/04otm4_fgokbm.js +1 -0
  94. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/056s9zs336jxm.js +1 -0
  95. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/059hv_g8zqixk.js +1 -0
  96. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/05vemp8yxojjb.js +1 -0
  97. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/06fhfy8eo--2j.js +1 -0
  98. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/077munl7atdzy.js +1 -0
  99. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/07_sztjw2zw03.css +1 -0
  100. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/07o5~zvw9clgj.js +1 -0
  101. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/07uz2g0_38qia.js +4 -0
  102. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/08tnvfuv3_5o9.js +1 -0
  103. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0b7arnzv89h-j.js +1 -0
  104. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0be78tz0a5au1.js +1 -0
  105. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0cimpi4vr37_t.js +1 -0
  106. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0f.~cy-4z32-5.js +1 -0
  107. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0fi6roa6ocwc9.js +1 -0
  108. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0gz_3mtiwcabp.js +1 -0
  109. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0ilwwu75d3vfp.js +1 -0
  110. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0im_i0ss6zxet.js +2 -0
  111. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0ka051yepewro.js +1 -0
  112. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0n~dq4kpx9xxx.js +1 -0
  113. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0p6~n93_~j4rn.js +1 -0
  114. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0pl2cmnllqj7b.js +1 -0
  115. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0qn5stkkf4xy1.js +1 -0
  116. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0rdmkomuytin5.js +1 -0
  117. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0spq~zna~mrc6.js +1 -0
  118. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0tqco-emg.3ff.js +1 -0
  119. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0umzvrml~lr94.js +1 -0
  120. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0v.c9j1.kuiv~.js +1 -0
  121. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/0ze4gu236oq96.js +31 -0
  122. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/10.6-2w1agbgg.js +1 -0
  123. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/11.ke-gavch7..js +1 -0
  124. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/138xf6ysy85-9.js +1 -0
  125. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/14s3dpmeh6ra3.js +1 -0
  126. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/15bxb1da~ccz9.js +1 -0
  127. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/16rwsqira6kf6.js +1 -0
  128. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/17p96j1ofg3lm.js +1 -0
  129. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/180zy~f3la25~.js +1 -0
  130. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/chunks/turbopack-0xgsc5fkkqcj~.js +1 -0
  131. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/media/icon.0f8r8fb1xdir..svg +21 -0
  132. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/uVYYN_m9Y6LX0BhAeduWk/_buildManifest.js +11 -0
  133. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/uVYYN_m9Y6LX0BhAeduWk/_clientMiddlewareManifest.js +1 -0
  134. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_next/static/uVYYN_m9Y6LX0BhAeduWk/_ssgManifest.js +1 -0
  135. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._full.txt +18 -0
  136. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._head.txt +6 -0
  137. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._index.txt +7 -0
  138. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._not-found.__PAGE__.txt +6 -0
  139. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._not-found.txt +5 -0
  140. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/__next._tree.txt +2 -0
  141. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/index.html +1 -0
  142. lumonox-0.2.5/src/lumonox_backend/dashboard_static/_not-found/index.txt +18 -0
  143. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next.!KG1haW4p.alerts.__PAGE__.txt +7 -0
  144. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next.!KG1haW4p.alerts.txt +5 -0
  145. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next.!KG1haW4p.txt +9 -0
  146. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._full.txt +26 -0
  147. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._head.txt +6 -0
  148. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._index.txt +7 -0
  149. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/__next._tree.txt +2 -0
  150. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/index.html +1 -0
  151. lumonox-0.2.5/src/lumonox_backend/dashboard_static/alerts/index.txt +26 -0
  152. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._full.txt +20 -0
  153. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._head.txt +6 -0
  154. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._index.txt +7 -0
  155. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next._tree.txt +2 -0
  156. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next.auth.magic-link.__PAGE__.txt +7 -0
  157. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next.auth.magic-link.txt +5 -0
  158. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/__next.auth.txt +5 -0
  159. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/index.html +1 -0
  160. lumonox-0.2.5/src/lumonox_backend/dashboard_static/auth/magic-link/index.txt +20 -0
  161. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next.!KG1haW4p.bookmarks.__PAGE__.txt +7 -0
  162. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next.!KG1haW4p.bookmarks.txt +5 -0
  163. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next.!KG1haW4p.txt +9 -0
  164. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._full.txt +26 -0
  165. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._head.txt +6 -0
  166. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._index.txt +7 -0
  167. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/__next._tree.txt +2 -0
  168. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/index.html +1 -0
  169. lumonox-0.2.5/src/lumonox_backend/dashboard_static/bookmarks/index.txt +26 -0
  170. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next.!KG1haW4p.dashboard.__PAGE__.txt +7 -0
  171. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next.!KG1haW4p.dashboard.txt +5 -0
  172. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next.!KG1haW4p.txt +9 -0
  173. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._full.txt +28 -0
  174. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._head.txt +6 -0
  175. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._index.txt +7 -0
  176. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/__next._tree.txt +2 -0
  177. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/index.html +1 -0
  178. lumonox-0.2.5/src/lumonox_backend/dashboard_static/dashboard/index.txt +28 -0
  179. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next.!KG1haW4p.diagnosis.__PAGE__.txt +7 -0
  180. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next.!KG1haW4p.diagnosis.txt +5 -0
  181. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next.!KG1haW4p.txt +9 -0
  182. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._full.txt +25 -0
  183. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._head.txt +6 -0
  184. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._index.txt +7 -0
  185. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/__next._tree.txt +2 -0
  186. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/index.html +1 -0
  187. lumonox-0.2.5/src/lumonox_backend/dashboard_static/diagnosis/index.txt +25 -0
  188. lumonox-0.2.5/src/lumonox_backend/dashboard_static/icon.svg +21 -0
  189. lumonox-0.2.5/src/lumonox_backend/dashboard_static/index.html +1 -0
  190. lumonox-0.2.5/src/lumonox_backend/dashboard_static/index.txt +20 -0
  191. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next.!KG1haW4p.logs.__PAGE__.txt +6 -0
  192. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next.!KG1haW4p.logs.txt +5 -0
  193. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next.!KG1haW4p.txt +9 -0
  194. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._full.txt +23 -0
  195. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._head.txt +6 -0
  196. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._index.txt +7 -0
  197. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/__next._tree.txt +2 -0
  198. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/index.html +1 -0
  199. lumonox-0.2.5/src/lumonox_backend/dashboard_static/logs/index.txt +23 -0
  200. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next.!KG1haW4p.onboarding.__PAGE__.txt +7 -0
  201. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next.!KG1haW4p.onboarding.txt +5 -0
  202. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next.!KG1haW4p.txt +9 -0
  203. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._full.txt +27 -0
  204. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._head.txt +6 -0
  205. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._index.txt +7 -0
  206. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/__next._tree.txt +2 -0
  207. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/index.html +1 -0
  208. lumonox-0.2.5/src/lumonox_backend/dashboard_static/onboarding/index.txt +27 -0
  209. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next.!KG1haW4p.query-explorer.__PAGE__.txt +7 -0
  210. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next.!KG1haW4p.query-explorer.txt +5 -0
  211. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next.!KG1haW4p.txt +9 -0
  212. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._full.txt +26 -0
  213. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._head.txt +6 -0
  214. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._index.txt +7 -0
  215. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/__next._tree.txt +2 -0
  216. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/index.html +1 -0
  217. lumonox-0.2.5/src/lumonox_backend/dashboard_static/query-explorer/index.txt +26 -0
  218. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next.!KG1haW4p.requests.__PAGE__.txt +7 -0
  219. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next.!KG1haW4p.requests.txt +5 -0
  220. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next.!KG1haW4p.txt +9 -0
  221. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._full.txt +26 -0
  222. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._head.txt +6 -0
  223. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._index.txt +7 -0
  224. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/__next._tree.txt +2 -0
  225. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/index.html +1 -0
  226. lumonox-0.2.5/src/lumonox_backend/dashboard_static/requests/index.txt +26 -0
  227. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next.!KG1haW4p.settings.__PAGE__.txt +7 -0
  228. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next.!KG1haW4p.settings.txt +5 -0
  229. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next.!KG1haW4p.txt +9 -0
  230. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._full.txt +26 -0
  231. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._head.txt +6 -0
  232. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._index.txt +7 -0
  233. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/__next._tree.txt +2 -0
  234. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/index.html +1 -0
  235. lumonox-0.2.5/src/lumonox_backend/dashboard_static/settings/index.txt +26 -0
  236. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next.!KG1haW4p.traces.__PAGE__.txt +7 -0
  237. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next.!KG1haW4p.traces.txt +5 -0
  238. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next.!KG1haW4p.txt +9 -0
  239. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._full.txt +26 -0
  240. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._head.txt +6 -0
  241. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._index.txt +7 -0
  242. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/__next._tree.txt +2 -0
  243. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/index.html +1 -0
  244. lumonox-0.2.5/src/lumonox_backend/dashboard_static/traces/index.txt +26 -0
  245. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.txt +9 -0
  246. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.w.$d$pageId.__PAGE__.txt +7 -0
  247. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.w.$d$pageId.txt +5 -0
  248. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next.!KG1haW4p.w.txt +5 -0
  249. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._full.txt +27 -0
  250. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._head.txt +6 -0
  251. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._index.txt +7 -0
  252. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/__next._tree.txt +2 -0
  253. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/index.html +1 -0
  254. lumonox-0.2.5/src/lumonox_backend/dashboard_static/w/lx_showcase/index.txt +27 -0
  255. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next.!KG1haW4p.txt +9 -0
  256. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next.!KG1haW4p.widgets.__PAGE__.txt +6 -0
  257. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next.!KG1haW4p.widgets.txt +5 -0
  258. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._full.txt +24 -0
  259. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._head.txt +6 -0
  260. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._index.txt +7 -0
  261. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/__next._tree.txt +2 -0
  262. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/index.html +1 -0
  263. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets/index.txt +24 -0
  264. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next.!KG1haW4p.txt +9 -0
  265. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next.!KG1haW4p.widgets-showcase.__PAGE__.txt +6 -0
  266. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next.!KG1haW4p.widgets-showcase.txt +5 -0
  267. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._full.txt +24 -0
  268. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._head.txt +6 -0
  269. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._index.txt +7 -0
  270. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/__next._tree.txt +2 -0
  271. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/index.html +1 -0
  272. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showcase/index.txt +24 -0
  273. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next.!KG1haW4p.txt +9 -0
  274. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next.!KG1haW4p.widgets-showroom.__PAGE__.txt +6 -0
  275. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next.!KG1haW4p.widgets-showroom.txt +5 -0
  276. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._full.txt +24 -0
  277. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._head.txt +6 -0
  278. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._index.txt +7 -0
  279. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/__next._tree.txt +2 -0
  280. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/index.html +1 -0
  281. lumonox-0.2.5/src/lumonox_backend/dashboard_static/widgets-showroom/index.txt +24 -0
  282. lumonox-0.2.5/src/lumonox_backend/database/__init__.py +19 -0
  283. lumonox-0.2.5/src/lumonox_backend/database/migrations.py +95 -0
  284. lumonox-0.2.5/src/lumonox_backend/database/session.py +99 -0
  285. lumonox-0.2.5/src/lumonox_backend/ingestion/__init__.py +1 -0
  286. lumonox-0.2.5/src/lumonox_backend/ingestion/body_size.py +138 -0
  287. lumonox-0.2.5/src/lumonox_backend/ingestion/exclude_lumonox.py +60 -0
  288. lumonox-0.2.5/src/lumonox_backend/ingestion/limits.py +62 -0
  289. lumonox-0.2.5/src/lumonox_backend/ingestion/otlp_traces.py +242 -0
  290. lumonox-0.2.5/src/lumonox_backend/ingestion/scenario_events.py +307 -0
  291. lumonox-0.2.5/src/lumonox_backend/jobs/__init__.py +719 -0
  292. lumonox-0.2.5/src/lumonox_backend/jobs/__main__.py +8 -0
  293. lumonox-0.2.5/src/lumonox_backend/lifespan.py +382 -0
  294. lumonox-0.2.5/src/lumonox_backend/main.py +5 -0
  295. lumonox-0.2.5/src/lumonox_backend/maintenance/__init__.py +8 -0
  296. lumonox-0.2.5/src/lumonox_backend/maintenance/retention.py +310 -0
  297. lumonox-0.2.5/src/lumonox_backend/maintenance/retention_constants.py +6 -0
  298. lumonox-0.2.5/src/lumonox_backend/maintenance/retention_deletes.py +236 -0
  299. lumonox-0.2.5/src/lumonox_backend/maintenance/retention_duckdb.py +179 -0
  300. lumonox-0.2.5/src/lumonox_backend/maintenance/retention_sqlite.py +230 -0
  301. lumonox-0.2.5/src/lumonox_backend/metrics/__init__.py +57 -0
  302. lumonox-0.2.5/src/lumonox_backend/middleware/__init__.py +1 -0
  303. lumonox-0.2.5/src/lumonox_backend/middleware/dashboard_origin.py +63 -0
  304. lumonox-0.2.5/src/lumonox_backend/middleware/gzip_request_body.py +164 -0
  305. lumonox-0.2.5/src/lumonox_backend/models/__init__.py +55 -0
  306. lumonox-0.2.5/src/lumonox_backend/models/orm.py +620 -0
  307. lumonox-0.2.5/src/lumonox_backend/realtime/__init__.py +285 -0
  308. lumonox-0.2.5/src/lumonox_backend/realtime/bus.py +209 -0
  309. lumonox-0.2.5/src/lumonox_backend/realtime/dashboard_snapshot_reconcile.py +84 -0
  310. lumonox-0.2.5/src/lumonox_backend/realtime/dashboard_snapshot_store.py +118 -0
  311. lumonox-0.2.5/src/lumonox_backend/realtime/dashboard_ws_tick.py +80 -0
  312. lumonox-0.2.5/src/lumonox_backend/repositories/__init__.py +1 -0
  313. lumonox-0.2.5/src/lumonox_backend/repositories/aggregates.py +120 -0
  314. lumonox-0.2.5/src/lumonox_backend/repositories/alert_dispatches.py +39 -0
  315. lumonox-0.2.5/src/lumonox_backend/repositories/alert_settings.py +40 -0
  316. lumonox-0.2.5/src/lumonox_backend/repositories/dashboard_widgets.py +172 -0
  317. lumonox-0.2.5/src/lumonox_backend/repositories/events.py +100 -0
  318. lumonox-0.2.5/src/lumonox_backend/repositories/ingest_reliability.py +339 -0
  319. lumonox-0.2.5/src/lumonox_backend/repositories/metric_bucket_overview.py +81 -0
  320. lumonox-0.2.5/src/lumonox_backend/repositories/runtime_controls.py +116 -0
  321. lumonox-0.2.5/src/lumonox_backend/routes/__init__.py +1 -0
  322. lumonox-0.2.5/src/lumonox_backend/routes/dev_scenarios.py +118 -0
  323. lumonox-0.2.5/src/lumonox_backend/routes/ingest.py +521 -0
  324. lumonox-0.2.5/src/lumonox_backend/routes/rum.py +68 -0
  325. lumonox-0.2.5/src/lumonox_backend/schemas/__init__.py +180 -0
  326. lumonox-0.2.5/src/lumonox_backend/schemas/dashboard.py +777 -0
  327. lumonox-0.2.5/src/lumonox_backend/schemas/dashboard_overview_models.py +227 -0
  328. lumonox-0.2.5/src/lumonox_backend/schemas/ingest.py +57 -0
  329. lumonox-0.2.5/src/lumonox_backend/schemas/rum.py +48 -0
  330. lumonox-0.2.5/src/lumonox_backend/services/__init__.py +1 -0
  331. lumonox-0.2.5/src/lumonox_backend/services/aggregate_delta_codec.py +103 -0
  332. lumonox-0.2.5/src/lumonox_backend/services/alert_delivery.py +610 -0
  333. lumonox-0.2.5/src/lumonox_backend/services/alert_service.py +249 -0
  334. lumonox-0.2.5/src/lumonox_backend/services/duckdb_async.py +155 -0
  335. lumonox-0.2.5/src/lumonox_backend/services/event_plane_compactor.py +468 -0
  336. lumonox-0.2.5/src/lumonox_backend/services/event_plane_compactor_worker.py +110 -0
  337. lumonox-0.2.5/src/lumonox_backend/services/event_plane_manifest.py +208 -0
  338. lumonox-0.2.5/src/lumonox_backend/services/event_plane_parity.py +233 -0
  339. lumonox-0.2.5/src/lumonox_backend/services/event_plane_read_path.py +55 -0
  340. lumonox-0.2.5/src/lumonox_backend/services/event_plane_shards.py +434 -0
  341. lumonox-0.2.5/src/lumonox_backend/services/event_store.py +1001 -0
  342. lumonox-0.2.5/src/lumonox_backend/services/event_store_utils.py +31 -0
  343. lumonox-0.2.5/src/lumonox_backend/services/infrastructure_metrics.py +146 -0
  344. lumonox-0.2.5/src/lumonox_backend/services/ingest_aggregate_worker.py +143 -0
  345. lumonox-0.2.5/src/lumonox_backend/services/ingest_service.py +507 -0
  346. lumonox-0.2.5/src/lumonox_backend/services/ingest_sql_tail_codec.py +131 -0
  347. lumonox-0.2.5/src/lumonox_backend/services/ingest_widgets.py +308 -0
  348. lumonox-0.2.5/src/lumonox_backend/services/parquet_exporter.py +220 -0
  349. lumonox-0.2.5/src/lumonox_backend/services/parquet_lifecycle.py +231 -0
  350. lumonox-0.2.5/src/lumonox_backend/services/parquet_object_storage.py +393 -0
  351. lumonox-0.2.5/src/lumonox_backend/services/project_activity.py +41 -0
  352. lumonox-0.2.5/tests/conftest.py +98 -0
  353. lumonox-0.2.5/tests/db_reset.py +85 -0
  354. lumonox-0.2.5/tests/test_alert_window_semantics_parity.py +253 -0
  355. lumonox-0.2.5/tests/test_alerts.py +363 -0
  356. lumonox-0.2.5/tests/test_app_health.py +331 -0
  357. lumonox-0.2.5/tests/test_backend_jobs.py +431 -0
  358. lumonox-0.2.5/tests/test_commercial_plan_limits.py +11 -0
  359. lumonox-0.2.5/tests/test_dashboard.py +1582 -0
  360. lumonox-0.2.5/tests/test_dashboard_auth.py +1230 -0
  361. lumonox-0.2.5/tests/test_dashboard_query_bundle.py +158 -0
  362. lumonox-0.2.5/tests/test_dashboard_query_snapshot_cache.py +159 -0
  363. lumonox-0.2.5/tests/test_dashboard_read_rate_limit.py +84 -0
  364. lumonox-0.2.5/tests/test_dashboard_rum.py +52 -0
  365. lumonox-0.2.5/tests/test_dashboard_snapshot_reconcile.py +65 -0
  366. lumonox-0.2.5/tests/test_dashboard_snapshot_store.py +56 -0
  367. lumonox-0.2.5/tests/test_database_url_redaction.py +17 -0
  368. lumonox-0.2.5/tests/test_deployment_settings.py +316 -0
  369. lumonox-0.2.5/tests/test_dev_scenarios.py +129 -0
  370. lumonox-0.2.5/tests/test_drill_catalog_docs.py +25 -0
  371. lumonox-0.2.5/tests/test_dx_docs_parity.py +39 -0
  372. lumonox-0.2.5/tests/test_event_plane_compactor.py +589 -0
  373. lumonox-0.2.5/tests/test_event_plane_compactor_worker.py +57 -0
  374. lumonox-0.2.5/tests/test_event_plane_config.py +113 -0
  375. lumonox-0.2.5/tests/test_event_plane_manifest.py +124 -0
  376. lumonox-0.2.5/tests/test_event_plane_parity.py +221 -0
  377. lumonox-0.2.5/tests/test_event_plane_read_path.py +97 -0
  378. lumonox-0.2.5/tests/test_event_plane_shards.py +242 -0
  379. lumonox-0.2.5/tests/test_event_store_duckdb.py +515 -0
  380. lumonox-0.2.5/tests/test_event_store_duckdb_path_normalization.py +79 -0
  381. lumonox-0.2.5/tests/test_gzip_request_body_middleware.py +94 -0
  382. lumonox-0.2.5/tests/test_ingest.py +1121 -0
  383. lumonox-0.2.5/tests/test_ingest_body_size.py +129 -0
  384. lumonox-0.2.5/tests/test_ingest_event_plane_shadow.py +200 -0
  385. lumonox-0.2.5/tests/test_ingest_sql_tail_codec.py +145 -0
  386. lumonox-0.2.5/tests/test_ingest_widget_points.py +38 -0
  387. lumonox-0.2.5/tests/test_integration_flow.py +312 -0
  388. lumonox-0.2.5/tests/test_overview_derived_widgets.py +125 -0
  389. lumonox-0.2.5/tests/test_overview_metric_bucket_merge.py +80 -0
  390. lumonox-0.2.5/tests/test_parquet_config_paths.py +33 -0
  391. lumonox-0.2.5/tests/test_parquet_exporter.py +319 -0
  392. lumonox-0.2.5/tests/test_parquet_lifecycle.py +304 -0
  393. lumonox-0.2.5/tests/test_parquet_object_storage.py +382 -0
  394. lumonox-0.2.5/tests/test_query_explorer_validation.py +35 -0
  395. lumonox-0.2.5/tests/test_realtime_bus.py +103 -0
  396. lumonox-0.2.5/tests/test_release_gates_manifest.py +46 -0
  397. lumonox-0.2.5/tests/test_retention.py +708 -0
  398. lumonox-0.2.5/tests/test_retention_pressure.py +129 -0
  399. lumonox-0.2.5/tests/test_runtime_controls.py +84 -0
  400. lumonox-0.2.5/tests/test_sqlite_local_defaults.py +142 -0
  401. lumonox-0.2.5/tests/test_studio_nav_and_showcase.py +63 -0
  402. lumonox-0.2.5/tests/test_widget_layout.py +41 -0
  403. lumonox-0.2.5/tests/test_widget_points_selection.py +55 -0
@@ -0,0 +1,126 @@
1
+ # Copy to backend/.env and fill in values (never commit backend/.env).
2
+ # Synthetic stack: scripts/run_synthetic_stack.sh sources this file before
3
+ # `npm --prefix frontend run build`, so set NEXT_PUBLIC_* here first.
4
+ #
5
+ # Local / uvicorn: the backend loads this file automatically when it exists
6
+ # next to this package (repo ``backend/.env``) or set LUMONOX_BACKEND_DOTENV to an absolute path.
7
+
8
+ # Relational DB (projects, keys, dashboard metadata). Path is anchored to repo root / LUMONOX_DATA_DIR
9
+ # (see normalize_database_url); keep alongside DuckDB under .lumonox/.
10
+ DATABASE_URL=sqlite+aiosqlite:///./.lumonox/lumonox.db
11
+ # Multi-replica production: run migrations once as a deploy step, then set to false on API replicas.
12
+ # DATABASE_RUN_MIGRATIONS_ON_STARTUP=false
13
+
14
+ # Raw HTTP events (local / synthetic stack)
15
+ LUMONOX_EVENT_STORE=duckdb
16
+ LUMONOX_EVENT_PLANE_MODE=duckdb_single_writer
17
+ # Relative paths are anchored to the repo root (parent of backend/), not process cwd.
18
+ # Prefer an absolute path in production. Override anchor with LUMONOX_DATA_DIR if needed.
19
+ # Relative paths anchor to data root (LUMONOX_DATA_DIR / LUMONOX_PROJECT_ROOT, else monorepo root).
20
+ # Prefer an absolute path in production. See resolve_lumonox_data_root in core/config.py.
21
+ LUMONOX_DUCKDB_PATH=.lumonox/events.duckdb
22
+ # Plan B (duckdb_log_shards): append-only shard storage root.
23
+ # Relative paths are anchored to data root (same as LUMONOX_DUCKDB_PATH).
24
+ LUMONOX_EVENT_PLANE_SHARDS_PATH=.lumonox/events-log
25
+ # Plan B compactor snapshot output root.
26
+ LUMONOX_EVENT_PLANE_SNAPSHOTS_PATH=.lumonox/events-duckdb
27
+ # Optional phase-1 cold export (DuckDB -> partitioned Parquet):
28
+ # LUMONOX_PARQUET_EXPORT_ENABLED=true
29
+ # LUMONOX_PARQUET_EXPORT_ROOT=.lumonox/parquet/events
30
+ # LUMONOX_PARQUET_EXPORT_INTERVAL_SECONDS=300
31
+ # LUMONOX_PARQUET_EXPORT_WINDOW_SECONDS=900
32
+ # Optional phase-2 hybrid read path (DuckDB hot + Parquet cold):
33
+ # LUMONOX_PARQUET_QUERY_ENABLED=true
34
+ # LUMONOX_PARQUET_HOT_WINDOW_HOURS=24
35
+ # Optional phase-3 lifecycle (compaction + retention + readability verification):
36
+ # LUMONOX_PARQUET_LIFECYCLE_ENABLED=true
37
+ # LUMONOX_PARQUET_LIFECYCLE_INTERVAL_SECONDS=3600
38
+ # LUMONOX_PARQUET_LIFECYCLE_RETENTION_DAYS=90
39
+ # LUMONOX_PARQUET_LIFECYCLE_DRY_RUN=false
40
+ # LUMONOX_PARQUET_LIFECYCLE_COMPACTION_MIN_FILES=4
41
+ # LUMONOX_PARQUET_LIFECYCLE_VERIFY_SAMPLE_SIZE=5
42
+ # Optional phase-4 object storage snapshots + DR restore:
43
+ # LUMONOX_PARQUET_OBJECT_STORAGE_ENABLED=true
44
+ # s3:// URIs need boto3 (Dockerfile installs backend[parquet-s3]; locally: pip install -e "./backend[parquet-s3]").
45
+ # LUMONOX_PARQUET_OBJECT_STORAGE_URI=s3://my-bucket/lumonox
46
+ # LUMONOX_PARQUET_OBJECT_STORAGE_PREFIX=parquet-events
47
+ # LUMONOX_PARQUET_OBJECT_STORAGE_INTERVAL_SECONDS=900
48
+ # LUMONOX_PARQUET_OBJECT_STORAGE_VERIFY_UPLOAD=true
49
+ # LUMONOX_PARQUET_OBJECT_STORAGE_ENDPOINT_URL=https://s3.us-east-1.amazonaws.com
50
+ # LUMONOX_PARQUET_OBJECT_STORAGE_REGION=us-east-1
51
+ # LUMONOX_PARQUET_OBJECT_STORAGE_ACCESS_KEY_ID=
52
+ # LUMONOX_PARQUET_OBJECT_STORAGE_SECRET_ACCESS_KEY=
53
+ # LUMONOX_PARQUET_OBJECT_STORAGE_SESSION_TOKEN=
54
+ # LUMONOX_PARQUET_OBJECT_STORAGE_RESTORE_ROOT=.lumonox/parquet/restore
55
+ # LUMONOX_PARQUET_OBJECT_STORAGE_RESTORE_MANIFEST_KEY=
56
+ # Optional Plan B knobs (kept conservative by default):
57
+ # LUMONOX_SHARD_MAX_BYTES=134217728
58
+ # LUMONOX_SHARD_MAX_AGE_SECONDS=300
59
+ # LUMONOX_COMPACTOR_INTERVAL_SECONDS=60
60
+ # LUMONOX_COMPACTOR_MAX_CONCURRENCY=1
61
+ # LUMONOX_COMPACTOR_MAX_SHARDS_PER_RUN=1024
62
+ # LUMONOX_COMPACTOR_PUBLISH_TIMEOUT_SECONDS=60
63
+ # LUMONOX_SNAPSHOT_RETENTION_COUNT=3
64
+ # LUMONOX_EVENT_PLANE_BACKPRESSURE_MIN_FREE_BYTES=536870912
65
+ # LUMONOX_EVENT_PLANE_BACKPRESSURE_MIN_FREE_PERCENT=5
66
+ # LUMONOX_EVENT_PLANE_BACKPRESSURE_MAX_PENDING_SHARDS=20000
67
+ # LUMONOX_DATA_DIR=/srv/lumonox
68
+
69
+ # Scheduler defaults:
70
+ # - Local default SQLite filename (.lumonox/lumonox.db) auto-enables scheduler when unset.
71
+ # - Non-default SQLite/Postgres should set JOBS_ENABLE_SCHEDULER=true explicitly (or run external cron jobs).
72
+ # JOBS_ENABLE_SCHEDULER=true
73
+
74
+ # Production topology baseline (recommended explicit values):
75
+ # LUMONOX_ENV=production
76
+ # LUMONOX_EVENT_STORE=duckdb
77
+ # LUMONOX_EVENT_PLANE_MODE=duckdb_single_writer
78
+ # LUMONOX_DUCKDB_SINGLE_WRITER_PROFILE=true # required ack for single-writer DuckDB in production
79
+ # JOBS_ENABLE_SCHEDULER=true
80
+ # DASHBOARD_AUTH_ENABLED=true
81
+ # DASHBOARD_ENFORCE_ORIGIN_FOR_MUTATIONS=true
82
+ # DASHBOARD_REALTIME_BUS_BACKEND=postgres_notify # set "none" for single-replica + sticky WS
83
+ # DATABASE_RUN_MIGRATIONS_ON_STARTUP=false # run one-shot migration before scaling replicas
84
+
85
+ # Dashboard read/query protection for expensive endpoints (set 0 to disable):
86
+ # DASHBOARD_READ_RATE_LIMIT_REQUESTS_PER_WINDOW=120
87
+ # DASHBOARD_READ_RATE_LIMIT_WINDOW_SECONDS=60
88
+ # Optional cross-replica WS propagation on Postgres:
89
+ # DASHBOARD_REALTIME_BUS_BACKEND=postgres_notify
90
+ # DASHBOARD_REALTIME_BUS_CHANNEL=lumonox_dashboard_realtime
91
+ # Optional realtime snapshot + WS delta protocol (fallback remains POST /dashboard/query):
92
+ # LUMONOX_DASHBOARD_REALTIME_ENABLED=false
93
+ # LUMONOX_DASHBOARD_REALTIME_WS_ENABLED=false
94
+ # LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_MAX_PROJECTS=512
95
+ # LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_TTL_SECONDS=900
96
+ # LUMONOX_DASHBOARD_REALTIME_MAX_DELTA_QUEUE_PER_PROJECT=32
97
+ # LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_RECONCILE_INTERVAL_SECONDS=15
98
+ # LUMONOX_DASHBOARD_REALTIME_SNAPSHOT_MAX_DRIFT_VERSIONS=25
99
+ # LUMONOX_DASHBOARD_QUERY_SNAPSHOT_REFRESH_SECONDS=30
100
+ # LUMONOX_DASHBOARD_WS_LIVE_TICK_SECONDS=2
101
+ # NEXT_PUBLIC_LUMONOX_LIVE_DELTA_REFRESH_THROTTLE_MS=800
102
+ # NEXT_PUBLIC_LUMONOX_DASHBOARD_REALTIME_WS_ENABLED=false
103
+
104
+ # Optional dashboard frontend RUM ingest guardrails (`POST /lumonox/rum`):
105
+ # DASHBOARD_RUM_MAX_REQUEST_BYTES=8192
106
+ # DASHBOARD_RUM_LOG_PAYLOADS=false
107
+
108
+ # Browser origins allowed for credentialed dashboard calls (include every host:port you open the UI on).
109
+ # Default in code also allows 8000/8000; override if you use other ports.
110
+ # CORS_ALLOW_ORIGINS=http://127.0.0.1:8000,http://localhost:8000,http://127.0.0.1:8000,http://localhost:8000
111
+
112
+ # Dashboard magic link — use the SAME email you type on the sign-in screen (or use DASHBOARD_ALLOWED_EMAIL_DOMAINS).
113
+ # For local dev without email delivery, set DASHBOARD_AUTH_MAGIC_LINK_DEV_EXPOSE_TOKEN=true to get a link in the UI.
114
+ DASHBOARD_AUTH_ALLOWED_EMAIL=you@example.com
115
+ ALERT_EMAIL_PROVIDER=file
116
+ # Relative paths are anchored to the project/data root (see resolve_lumonox_data_root), not cwd.
117
+ ALERT_EMAIL_FILE_OUTBOX_DIR=./.lumonox/emails
118
+ ALERT_EMAIL_FROM=alerts@example.com
119
+ # DASHBOARD_AUTH_MAGIC_LINK_DEV_EXPOSE_TOKEN=true
120
+
121
+ # Optional: static UI env bundle — often repo-root .env.lumonox for Next builds
122
+ # NEXT_PUBLIC_LUMONOX_API_BASE_URL=/lumonox
123
+ # NEXT_PUBLIC_LUMONOX_API_KEY=…
124
+
125
+ # Optional: custom .env.lumonox path (default ./.env.lumonox)
126
+ # LUMONOX_ENV_FILE=/path/to/.env.lumonox
@@ -0,0 +1,73 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.so
6
+ .Python
7
+ build/
8
+ develop-eggs/
9
+ dist/
10
+ downloads/
11
+ eggs/
12
+ .eggs/
13
+ /lib/
14
+ lib64/
15
+ parts/
16
+ sdist/
17
+ var/
18
+ wheels/
19
+ *.egg-info/
20
+ .installed.cfg
21
+ *.egg
22
+ MANIFEST
23
+ .venv/
24
+ venv/
25
+ ENV/
26
+
27
+ # uv
28
+ .uv/
29
+
30
+ # Testing / coverage
31
+ .pytest_cache/
32
+ .coverage
33
+ htmlcov/
34
+ .mypy_cache/
35
+ .ruff_cache/
36
+
37
+ # Node / Next.js (frontend)
38
+ node_modules/
39
+ .next/
40
+ out/
41
+ .turbo/
42
+ *.tsbuildinfo
43
+
44
+ # OS / editors
45
+ .DS_Store
46
+ .idea/
47
+ .vscode/
48
+ *.swp
49
+ *.swo
50
+
51
+ # Environment
52
+ .env
53
+ .env.*
54
+ !.env.example
55
+ !.env.lumonox.example
56
+
57
+ # Local SQLite (dev)
58
+ *.db
59
+
60
+ # Logs
61
+ *.log
62
+
63
+ .lumonox/
64
+ # Legacy local data dir name (pre-rename checkouts); never commit.
65
+ .autopulse/
66
+ backend/.autopulse/
67
+ sdk/.autopulse/
68
+
69
+ # Local stray copies of dashboard static export (canonical build is frontend/out)
70
+ sdk/src/lumonox/ui/
71
+
72
+ # Hatch sdist staging for shipped wheels (never commit baked export under src)
73
+ backend/src/lumonox_backend/dashboard_static/
@@ -0,0 +1,59 @@
1
+ # Alert Delivery Verification Runbook
2
+
3
+ This runbook validates that alert delivery is configured and observable from the dashboard.
4
+
5
+ ## 1) Configure sender mode
6
+
7
+ Set one of the minimal sender configurations:
8
+
9
+ - Email provider (recommended first):
10
+ - `ALERT_SENDER_MODE=email`
11
+ - `ALERT_EMAIL_PROVIDER=resend` (or `postmark`)
12
+ - `ALERT_EMAIL_API_KEY=...`
13
+ - `ALERT_EMAIL_FROM=alerts@example.com`
14
+ - Email (zero-config dev outbox; writes `.eml` locally):
15
+ - `ALERT_SENDER_MODE=email`
16
+ - `ALERT_EMAIL_PROVIDER=file`
17
+ - optional: `ALERT_EMAIL_FILE_OUTBOX_DIR=./.lumonox/emails`
18
+ - optional: `ALERT_EMAIL_FROM=alerts@localhost`
19
+ - Email (no SaaS, requires local MTA present):
20
+ - `ALERT_SENDER_MODE=email`
21
+ - `ALERT_EMAIL_PROVIDER=sendmail`
22
+ - optional: `ALERT_SENDMAIL_PATH=/usr/sbin/sendmail`
23
+ - Email (no SaaS, requires SMTP server reachable):
24
+ - `ALERT_SENDER_MODE=email`
25
+ - `ALERT_EMAIL_PROVIDER=smtp`
26
+ - `ALERT_EMAIL_SMTP_HOST=127.0.0.1` (or your SMTP host)
27
+ - optional: `ALERT_EMAIL_SMTP_PORT=25`
28
+ - optional: `ALERT_EMAIL_SMTP_USE_TLS=true`
29
+ - optional: `ALERT_EMAIL_SMTP_USERNAME=...`
30
+ - optional: `ALERT_EMAIL_SMTP_PASSWORD=...`
31
+ - Slack webhook:
32
+ - `ALERT_SENDER_MODE=slack`
33
+ - `ALERT_SLACK_WEBHOOK_URL=...`
34
+ - Discord webhook:
35
+ - `ALERT_SENDER_MODE=discord`
36
+ - `ALERT_DISCORD_WEBHOOK_URL=...`
37
+ - Multi-channel:
38
+ - `ALERT_SENDER_MODE=composite`
39
+ - combine email + Slack and/or Discord vars above.
40
+
41
+ ## 2) Trigger one evaluation pass
42
+
43
+ ```bash
44
+ uv run python -m lumonox_backend.jobs alerts-once
45
+ ```
46
+
47
+ The command prints the number of successfully sent alerts in that run.
48
+
49
+ ## 3) Validate dispatch observability
50
+
51
+ Open the dashboard Alerts page and verify dispatch rows include:
52
+
53
+ - `status` (`sent`, `failed`, or `skipped`)
54
+ - `reason_code` for failures/skips
55
+ - `attempt_count`
56
+ - `delivered_at` (for successful sends)
57
+ - `provider_message_id` when available
58
+
59
+ Use the **Failed only** filter to quickly review actionable delivery failures.
lumonox-0.2.5/PKG-INFO ADDED
@@ -0,0 +1,20 @@
1
+ Metadata-Version: 2.4
2
+ Name: lumonox
3
+ Version: 0.2.5
4
+ Summary: Lumonox backend ingestion API
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: aiosqlite>=0.22.1
7
+ Requires-Dist: alembic>=1.14.0
8
+ Requires-Dist: asyncpg>=0.30.0
9
+ Requires-Dist: duckdb>=1.1.3
10
+ Requires-Dist: fastapi>=0.115.0
11
+ Requires-Dist: greenlet>=3.0.0
12
+ Requires-Dist: httpx>=0.27.0
13
+ Requires-Dist: psutil>=6.0.0
14
+ Requires-Dist: psycopg[binary]>=3.3.3
15
+ Requires-Dist: python-dotenv>=1.0.0
16
+ Requires-Dist: sqlalchemy>=2.0.36
17
+ Requires-Dist: uvicorn>=0.32.0
18
+ Requires-Dist: websockets>=12.0
19
+ Provides-Extra: parquet-s3
20
+ Requires-Dist: boto3>=1.34.0; extra == 'parquet-s3'
@@ -0,0 +1,138 @@
1
+ # Lumonox Backend
2
+
3
+ FastAPI backend for ingest, dashboard APIs, auth/session flows, alerts, retention jobs, and realtime updates.
4
+
5
+ Python **import name** is **`lumonox_backend`**. The **distribution / PyPI project name** is **`lumonox`** (API + pre-built dashboard static export bundled in the wheel). Use **`lumonox`** / **`lumonox-sdk`** for installs from PyPI.
6
+
7
+ ## What lives here
8
+
9
+ - Ingest API: `POST /ingest` with API-key authentication.
10
+ - Dashboard API: overview, requests, error groups, diagnosis, alerts, settings, log query, organizations.
11
+ - Dashboard auth: magic-link sign-in, cookie sessions, tenant bootstrap endpoint.
12
+ - Background jobs: alert evaluation and retention cleanup.
13
+ - Internal ops endpoints: health/ready and service metrics.
14
+
15
+ ## Install outside the monorepo
16
+
17
+ **One line (PyPI, after trusted publishing is enabled):**
18
+
19
+ ```bash
20
+ pip install lumonox
21
+ ```
22
+
23
+ ```bash
24
+ uv add lumonox
25
+ ```
26
+
27
+ **One line (Git — always works; pin `main` to a tag or SHA in production):**
28
+
29
+ ```bash
30
+ uv add "lumonox @ git+https://github.com/sintimaski/lumonox.git@main#subdirectory=backend"
31
+ ```
32
+
33
+ ```bash
34
+ pip install "lumonox @ git+https://github.com/sintimaski/lumonox.git@main#subdirectory=backend"
35
+ ```
36
+
37
+ **API + Fast instrumented app in one line:** use the SDK extra (see [sdk/README.md](../sdk/README.md)):
38
+
39
+ ```bash
40
+ pip install "lumonox-sdk[stack]"
41
+ ```
42
+
43
+ If Python **3.14** fails to resolve wheels, try **3.12 or 3.13**.
44
+
45
+ ### Wheel build (bundled dashboard)
46
+
47
+ The **`lumonox`** sdist/wheel ships the Next static export under **`lumonox_backend/dashboard_static/`** (mounted at **`/lumonox/ui/`** when **`LUMONOX_FRONTEND_STATIC_DIR`** is unset and **`index.html`** exists). Build **`frontend/out`** first (see **`scripts/run_synthetic_stack.sh`** for **`NEXT_PUBLIC_*`** defaults):
48
+
49
+ ```bash
50
+ ./backend/scripts/package_wheel.sh
51
+ ```
52
+
53
+ ## Run locally
54
+
55
+ From repository root:
56
+
57
+ ```bash
58
+ uv sync --group dev
59
+ # Loads backend/.env so retention/cap settings are actually applied.
60
+ uv run uvicorn lumonox_backend.main:app --env-file backend/.env --log-level info
61
+ ```
62
+
63
+ Backend defaults to `http://localhost:8000`.
64
+
65
+ ## Key environment variables
66
+
67
+ - `DATABASE_URL` (default SQLite file: `.lumonox/lumonox.db` under the repo root—same directory tree as DuckDB; see `normalize_database_url` in `core/config.py`)
68
+ - `LUMONOX_EVENT_STORE` (`duckdb` default; set `sqlite` to force legacy SQL event reads)
69
+ - `LUMONOX_DUCKDB_PATH` (DuckDB event store file; relative values anchor under `LUMONOX_DATA_DIR` / `LUMONOX_PROJECT_ROOT`, else monorepo root—see `normalize_event_store_duckdb_path` / `resolve_lumonox_data_root` in `core/config.py`; use absolute paths in production if you prefer)
70
+ - `LUMONOX_DATA_DIR` / `LUMONOX_PROJECT_ROOT` (optional; pins the root for relative DuckDB paths and keeps ingest/dashboard/CLI on one file regardless of shell cwd)
71
+ - `CORS_ALLOW_ORIGINS`
72
+ - `DASHBOARD_AUTH_ALLOWED_EMAIL`
73
+ - `DASHBOARD_AUTH_ALLOW_API_KEY_FALLBACK` (disabled by default; enable only for controlled non-browser flows)
74
+ - `INGEST_MAX_REQUEST_BYTES`
75
+ - `INGEST_RATE_LIMIT_REQUESTS_PER_WINDOW`
76
+ - `INGEST_RATE_LIMIT_WINDOW_SECONDS`
77
+ - `INGEST_DISTRIBUTED_RATE_LIMIT_ENABLED` (enables DB-backed distributed limiter)
78
+ - `INGEST_ASYNC_AGGREGATE_ENABLED` (keeps ingest hot path raw-write-first)
79
+ - `INGEST_ASYNC_AGGREGATE_QUEUE_MAX_SIZE`
80
+ - `INGEST_DROP_LUMONOX_TRAFFIC_FROM_DB` (drops `/lumonox/*`, `/dashboard/*`, and `/ingest` events before persistence)
81
+ - `JOBS_ENABLE_SCHEDULER`
82
+ - `JOBS_RETENTION_INTERVAL_SECONDS` (minimum **5**; periodic `run_retention_cleanup_once` when scheduler or retention-only loop runs)
83
+ - `JOBS_SCHEDULER_LEASE_ENABLED` (prevents duplicate periodic job execution across instances)
84
+ - `JOBS_SCHEDULER_LEASE_TTL_SECONDS`
85
+ - `LUMONOX_ENV_FILE` (optional path to the `.env.lumonox` bundle for local static UI builds; default `./.env.lumonox` at process cwd)
86
+ - `LUMONOX_SQLITE_MAX_DB_FILE_MB` (max SQLite log-store file size in MB; applies to DuckDB or SQLite when capped. For SQLite it includes main + `-wal` + `-shm`; deprecated alias `LUMONOX_EMBEDDED_MAX_DB_SIZE_MB`; default **512** on dev default SQLite filenames when unset)
87
+ - `LUMONOX_RETENTION_PRESSURE_POLL_SECONDS` / `LUMONOX_RETENTION_PRESSURE_MIN_INTERVAL_SECONDS` (SQLite pressure poll; see `core/config.py`)
88
+
89
+ Parquet **object storage** with `LUMONOX_PARQUET_OBJECT_STORAGE_URI=s3://...` needs **`boto3`**. Install the extra from this directory (`uv pip install -e ".[parquet-s3]"`) or add `boto3` to your environment. `file://` URIs do not use `boto3`.
90
+
91
+ See `backend/src/lumonox_backend/core/config.py` and `backend/.env.example` for the complete list and defaults.
92
+
93
+ **Production:** startup applies `validate_deployment_settings` for `LUMONOX_ENV=production`. Follow `docs/ops/PRODUCTION_DEPLOYMENT.md` for enforced HTTPS ingest, internal metrics token, CORS, dashboard session/magic-link TTL, OIDC/magic-link URL schemes, and related constraints. Automated checks: `backend/tests/test_deployment_settings.py`.
94
+
95
+ ## Testing (backend)
96
+
97
+ - Unit-style deployment guardrails: `uv run pytest backend/tests/test_deployment_settings.py`
98
+ - Integration tests under `backend/tests/` use `BACKEND_TEST_DATABASE_URL` when set; otherwise `uv run pytest` uses an **isolated session SQLite file** under pytest’s basetemp (see `backend/tests/conftest.py`). Override explicitly, e.g. `export BACKEND_TEST_DATABASE_URL=sqlite+aiosqlite:////tmp/lx-test.db`, when you want a fixed path or Postgres.
99
+ - Ingest idempotency replay parity is required on Postgres (CI enforced). Local equivalent:
100
+ `export BACKEND_TEST_DATABASE_URL=postgresql+asyncpg://lumonox:lumonox@127.0.0.1:5432/lumonox_ci && uv run pytest backend/tests/test_ingest.py::test_ingest_idempotency_key_replays_accepted_without_duplicate_events -q`
101
+ - Backend CI-equivalent one-command gate (same backend checks split across `python-sqlite` + `python-postgres` CI jobs):
102
+ `export BACKEND_TEST_DATABASE_URL=postgresql+asyncpg://lumonox:lumonox@127.0.0.1:5432/lumonox_ci && make check-python-ci`
103
+
104
+ ## Retention scheduling (FastAPI-optional)
105
+
106
+ The portable unit of work is **one retention pass** (SQLite caps, time windows, aggregates trim):
107
+
108
+ | How | Command / API |
109
+ |-----|-----------------|
110
+ | **CLI (any OS, no web server)** | `cd backend && uv run python -m lumonox_backend.jobs retention-once` |
111
+ | **Sync Python (cron, Django command, systemd `ExecStart`)** | `from lumonox_backend.jobs import run_retention_sync` then `run_retention_sync()` after setting `DATABASE_URL` in the environment |
112
+ | **In-process (FastAPI)** | Lifespan starts the scheduler or retention-only loop; optional SQLite pressure poll |
113
+
114
+ For **Linux production** or **Django** (no FastAPI event loop), prefer **cron** or **systemd timers** calling the CLI or `run_retention_sync()` on the interval you want, and set `JOBS_ENABLE_SCHEDULER=false` on the API so you do not double-run retention in-process and from cron.
115
+
116
+ ## Event store migration helpers
117
+
118
+ - Backfill SQL `events` rows into the DuckDB event store:
119
+
120
+ ```bash
121
+ cd backend
122
+ uv run python scripts/backfill_events_to_duckdb.py --batch-size 1000
123
+ ```
124
+
125
+ Example cron every five minutes:
126
+
127
+ ```cron
128
+ */5 * * * * cd /path/to/lumonox/backend && /path/to/uv run python -m lumonox_backend.jobs retention-once >>/var/log/lumonox-retention.log 2>&1
129
+ ```
130
+
131
+ ## Operational runbooks
132
+
133
+ - Alert delivery setup and verification: `backend/ALERT_DELIVERY_RUNBOOK.md`
134
+ - Release/incident drill gates: `docs/runbooks/PHASE5_RELEASE_CHECKLIST.md` and `docs/runbooks/PHASE5_INCIDENT_DRILLS.md`
135
+
136
+ ## Scope and constraints
137
+
138
+ Product/architecture source of truth remains `DEVELOPMENT.md`.
@@ -0,0 +1,39 @@
1
+ [alembic]
2
+ script_location = src/lumonox_backend/alembic
3
+ path_separator = os
4
+ prepend_sys_path = ./src
5
+ # Overridden at runtime by alembic/env.py from get_settings() (loads backend/.env).
6
+ # Default app DB is SQLite under .lumonox/; Postgres requires DATABASE_URL in the environment.
7
+ sqlalchemy.url = sqlite:///./.lumonox/lumonox.db
8
+
9
+ [loggers]
10
+ keys = root,sqlalchemy,alembic
11
+
12
+ [handlers]
13
+ keys = console
14
+
15
+ [formatters]
16
+ keys = generic
17
+
18
+ [logger_root]
19
+ level = WARN
20
+ handlers = console
21
+
22
+ [logger_sqlalchemy]
23
+ level = WARN
24
+ handlers =
25
+ qualname = sqlalchemy.engine
26
+
27
+ [logger_alembic]
28
+ level = INFO
29
+ handlers =
30
+ qualname = alembic
31
+
32
+ [handler_console]
33
+ class = StreamHandler
34
+ args = (sys.stderr,)
35
+ level = NOTSET
36
+ formatter = generic
37
+
38
+ [formatter_generic]
39
+ format = %(levelname)-5.5s [%(name)s] %(message)s
@@ -0,0 +1,39 @@
1
+ [project]
2
+ name = "lumonox"
3
+ version = "0.2.5"
4
+ description = "Lumonox backend ingestion API"
5
+ requires-python = ">=3.11"
6
+ dependencies = [
7
+ "fastapi>=0.115.0",
8
+ "python-dotenv>=1.0.0",
9
+ "uvicorn>=0.32.0",
10
+ "websockets>=12.0",
11
+ "sqlalchemy>=2.0.36",
12
+ "greenlet>=3.0.0",
13
+ "asyncpg>=0.30.0",
14
+ "psycopg[binary]>=3.3.3",
15
+ "alembic>=1.14.0",
16
+ "aiosqlite>=0.22.1",
17
+ "duckdb>=1.1.3",
18
+ "psutil>=6.0.0",
19
+ "httpx>=0.27.0",
20
+ ]
21
+
22
+ [project.optional-dependencies]
23
+ # Parquet object storage with ``s3://`` URIs (``parquet_object_storage``); not needed for ``file://``.
24
+ parquet-s3 = [
25
+ "boto3>=1.34.0",
26
+ ]
27
+
28
+ [build-system]
29
+ requires = ["hatchling>=1.26.0"]
30
+ build-backend = "hatchling.build"
31
+
32
+ [tool.hatch.build.targets.sdist]
33
+ # Ship the Next static export inside the extracted tree used for wheels (nested under ``packages``)—avoids editable-install
34
+ # breakage from wheel-only forced paths while keeping ``frontend/out`` out of Git.
35
+ [tool.hatch.build.targets.sdist.force-include]
36
+ "../frontend/out" = "src/lumonox_backend/dashboard_static"
37
+
38
+ [tool.hatch.build.targets.wheel]
39
+ packages = ["src/lumonox_backend"]
@@ -0,0 +1,83 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ from datetime import UTC, datetime
6
+
7
+ from sqlalchemy import select
8
+
9
+ from lumonox_backend.database import AsyncSessionLocal
10
+ from lumonox_backend.models import Event
11
+ from lumonox_backend.services.duckdb_async import run_duckdb_write_sync
12
+ from lumonox_backend.services.event_store import get_duckdb_event_store
13
+
14
+
15
+ async def run_backfill(*, batch_size: int, since: datetime | None) -> int:
16
+ store = get_duckdb_event_store()
17
+ copied = 0
18
+ last_id = 0
19
+ while True:
20
+ async with AsyncSessionLocal() as session:
21
+ query = (
22
+ select(Event).where(Event.id > last_id).order_by(Event.id.asc()).limit(batch_size)
23
+ )
24
+ if since is not None:
25
+ query = query.where(Event.received_at >= since)
26
+ rows = (await session.scalars(query)).all()
27
+ if not rows:
28
+ break
29
+ payload = [
30
+ {
31
+ "project_id": row.project_id,
32
+ "timestamp": row.timestamp,
33
+ "received_at": row.received_at,
34
+ "sdk_version": row.sdk_version,
35
+ "type": row.type,
36
+ "service_name": row.service_name,
37
+ "environment": row.environment,
38
+ "method": row.method,
39
+ "path": row.path,
40
+ "status_code": row.status_code,
41
+ "latency_ms": row.latency_ms,
42
+ "payload": row.payload,
43
+ "request_id": row.request_id,
44
+ }
45
+ for row in rows
46
+ ]
47
+ await run_duckdb_write_sync(store.insert_rows, payload)
48
+ copied += len(rows)
49
+ last_id = int(rows[-1].id)
50
+ return copied
51
+
52
+
53
+ def parse_args() -> argparse.Namespace:
54
+ parser = argparse.ArgumentParser(description="Backfill SQL events into DuckDB event store")
55
+ parser.add_argument("--batch-size", type=int, default=1000)
56
+ parser.add_argument(
57
+ "--since",
58
+ type=str,
59
+ default="",
60
+ help="Optional ISO timestamp, e.g. 2026-04-01T00:00:00Z",
61
+ )
62
+ return parser.parse_args()
63
+
64
+
65
+ def _parse_since(raw: str) -> datetime | None:
66
+ value = raw.strip()
67
+ if not value:
68
+ return None
69
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
70
+ if parsed.tzinfo is None:
71
+ return parsed.replace(tzinfo=UTC)
72
+ return parsed.astimezone(UTC)
73
+
74
+
75
+ if __name__ == "__main__":
76
+ args = parse_args()
77
+ total = asyncio.run(
78
+ run_backfill(
79
+ batch_size=max(1, int(args.batch_size)),
80
+ since=_parse_since(args.since),
81
+ )
82
+ )
83
+ print(f"backfilled_events={total}")
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import asyncio
5
+ import sqlite3
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from sqlalchemy import func, select
10
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
11
+
12
+ from lumonox_backend.core.config import get_settings
13
+ from lumonox_backend.database import get_engine
14
+ from lumonox_backend.jobs import run_retention_once
15
+ from lumonox_backend.maintenance.retention import (
16
+ _resolve_sqlite_db_path,
17
+ _sqlite_db_disk_footprint_bytes,
18
+ sqlite_retention_pressure_pending,
19
+ )
20
+ from lumonox_backend.models import Event
21
+
22
+
23
+ def _fmt_mb(value: int) -> str:
24
+ return f"{value / (1024 * 1024):.2f}MB"
25
+
26
+
27
+ def _sqlite_vacuum_probe(db_path: Path) -> tuple[bool, str]:
28
+ try:
29
+ with sqlite3.connect(str(db_path), timeout=2.0) as connection:
30
+ connection.execute("PRAGMA busy_timeout=2000")
31
+ connection.execute("PRAGMA wal_checkpoint(TRUNCATE)")
32
+ connection.execute("VACUUM")
33
+ return True, "ok"
34
+ except sqlite3.OperationalError as exc:
35
+ return False, str(exc)
36
+
37
+
38
+ async def _run(iterations: int, sleep_seconds: float, dispose_engine_after: bool) -> None:
39
+ settings = get_settings()
40
+ db_path = _resolve_sqlite_db_path(settings.database_url)
41
+ if db_path is None:
42
+ raise SystemExit(f"DATABASE_URL is not file-backed sqlite: {settings.database_url}")
43
+
44
+ engine = get_engine(settings.database_url)
45
+ session_maker = async_sessionmaker(bind=engine, expire_on_commit=False, class_=AsyncSession)
46
+ print(f"db={db_path}")
47
+ print(
48
+ f"cap_mb={settings.sqlite_max_db_file_mb} "
49
+ f"interval={settings.jobs_retention_interval_seconds} "
50
+ f"pressure_poll={settings.retention_pressure_poll_seconds}"
51
+ )
52
+
53
+ for i in range(iterations):
54
+ async with session_maker() as session:
55
+ pending = await sqlite_retention_pressure_pending(session, settings)
56
+ event_count = int((await session.execute(select(func.count(Event.id)))).scalar_one())
57
+ before = _sqlite_db_disk_footprint_bytes(db_path)
58
+ start = time.monotonic()
59
+ deleted = await run_retention_once(
60
+ settings=settings, dispose_engine_after=dispose_engine_after
61
+ )
62
+ elapsed_ms = int((time.monotonic() - start) * 1000)
63
+ after = _sqlite_db_disk_footprint_bytes(db_path)
64
+ probe_ok, probe_msg = _sqlite_vacuum_probe(db_path)
65
+ print(
66
+ f"[{i + 1}/{iterations}] pending={pending} events={event_count} deleted={deleted} "
67
+ f"before={_fmt_mb(before)} after={_fmt_mb(after)} elapsed_ms={elapsed_ms} "
68
+ f"vacuum_probe={'ok' if probe_ok else 'locked'} ({probe_msg})"
69
+ )
70
+ if i < iterations - 1:
71
+ await asyncio.sleep(sleep_seconds)
72
+
73
+
74
+ def main() -> int:
75
+ parser = argparse.ArgumentParser(description="Debug scheduled retention + VACUUM behavior.")
76
+ parser.add_argument(
77
+ "--iterations", type=int, default=8, help="How many retention passes to run."
78
+ )
79
+ parser.add_argument(
80
+ "--sleep",
81
+ type=float,
82
+ default=2.0,
83
+ help="Sleep seconds between passes.",
84
+ )
85
+ parser.add_argument(
86
+ "--dispose-engine-after",
87
+ action=argparse.BooleanOptionalAction,
88
+ default=True,
89
+ help="Dispose shared async engine before VACUUM (default: true).",
90
+ )
91
+ args = parser.parse_args()
92
+ asyncio.run(_run(args.iterations, args.sleep, args.dispose_engine_after))
93
+ return 0
94
+
95
+
96
+ if __name__ == "__main__":
97
+ raise SystemExit(main())