stemtrace 0.3.5__tar.gz → 0.3.6__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 (154) hide show
  1. {stemtrace-0.3.5 → stemtrace-0.3.6}/.bumpversion.toml +1 -1
  2. {stemtrace-0.3.5 → stemtrace-0.3.6}/.github/dependabot.yml +10 -1
  3. stemtrace-0.3.6/.github/workflows/ci.yml +230 -0
  4. {stemtrace-0.3.5 → stemtrace-0.3.6}/.github/workflows/e2e.yml +26 -18
  5. {stemtrace-0.3.5 → stemtrace-0.3.6}/.github/workflows/release.yml +27 -20
  6. {stemtrace-0.3.5 → stemtrace-0.3.6}/CHANGELOG.md +32 -1
  7. stemtrace-0.3.6/Dockerfile +69 -0
  8. stemtrace-0.3.6/Dockerfile.e2e +37 -0
  9. {stemtrace-0.3.5 → stemtrace-0.3.6}/PKG-INFO +28 -5
  10. {stemtrace-0.3.5 → stemtrace-0.3.6}/README.md +23 -1
  11. {stemtrace-0.3.5 → stemtrace-0.3.6}/build_ui.py +44 -3
  12. stemtrace-0.3.6/docs/brand/stemtrace-mark-light.svg +1 -0
  13. stemtrace-0.3.6/docs/brand/stemtrace-mark.svg +1 -0
  14. {stemtrace-0.3.5 → stemtrace-0.3.6}/examples/fastapi_integration.py +1 -1
  15. {stemtrace-0.3.5 → stemtrace-0.3.6}/examples/with_auth.py +1 -1
  16. {stemtrace-0.3.5 → stemtrace-0.3.6}/examples/with_login.py +1 -1
  17. {stemtrace-0.3.5 → stemtrace-0.3.6}/pyproject.toml +7 -4
  18. stemtrace-0.3.6/scripts/wait_for_http.py +77 -0
  19. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/__init__.py +1 -1
  20. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/core/ports.py +21 -1
  21. stemtrace-0.3.6/src/stemtrace/library/transports/redis.py +381 -0
  22. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/api/routes.py +19 -3
  23. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/api/schemas.py +6 -0
  24. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/consumer.py +109 -12
  25. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/biome.json +1 -1
  26. stemtrace-0.3.6/src/stemtrace/server/ui/frontend/index.html +19 -0
  27. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/package-lock.json +679 -1227
  28. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/package.json +12 -6
  29. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/api/client.ts +1 -0
  30. stemtrace-0.3.6/src/stemtrace/server/ui/frontend/src/components/SproutMark.tsx +42 -0
  31. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/TimelineBar.tsx +1 -1
  32. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/__root.tsx +10 -4
  33. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/static.py +10 -1
  34. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/templates/login.html +4 -0
  35. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/conftest.py +2 -2
  36. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/test_fastapi_integration.py +30 -26
  37. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/test_rabbitmq_transport.py +3 -3
  38. stemtrace-0.3.6/tests/integration/test_redis_resilience_docker.py +205 -0
  39. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/test_redis_transport.py +7 -7
  40. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/test_static_ui.py +52 -0
  41. stemtrace-0.3.6/tests/integration/test_wait_for_http_redirects.py +80 -0
  42. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/test_workers_refresh.py +5 -3
  43. stemtrace-0.3.6/tests/unit/test_build_ui_hook.py +68 -0
  44. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_consumer.py +334 -13
  45. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_events.py +21 -21
  46. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_examples.py +12 -8
  47. stemtrace-0.3.6/tests/unit/test_favicon.py +48 -0
  48. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_form_login.py +26 -0
  49. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_graph.py +88 -88
  50. stemtrace-0.3.6/tests/unit/test_redis_resilience.py +424 -0
  51. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_routes.py +59 -6
  52. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_store.py +10 -10
  53. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_transports.py +48 -15
  54. stemtrace-0.3.6/tests/unit/test_wait_for_http_script.py +103 -0
  55. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_websocket.py +2 -2
  56. {stemtrace-0.3.5 → stemtrace-0.3.6}/uv.lock +123 -101
  57. stemtrace-0.3.5/.github/workflows/ci.yml +0 -189
  58. stemtrace-0.3.5/Dockerfile +0 -58
  59. stemtrace-0.3.5/Dockerfile.e2e +0 -27
  60. stemtrace-0.3.5/scripts/wait_for_http.py +0 -41
  61. stemtrace-0.3.5/src/stemtrace/library/transports/redis.py +0 -165
  62. stemtrace-0.3.5/src/stemtrace/server/ui/frontend/index.html +0 -16
  63. {stemtrace-0.3.5 → stemtrace-0.3.6}/.dockerignore +0 -0
  64. {stemtrace-0.3.5 → stemtrace-0.3.6}/.gitignore +0 -0
  65. {stemtrace-0.3.5 → stemtrace-0.3.6}/.pre-commit-config.yaml +0 -0
  66. {stemtrace-0.3.5 → stemtrace-0.3.6}/.python-version +0 -0
  67. {stemtrace-0.3.5 → stemtrace-0.3.6}/CONTRIBUTING.md +0 -0
  68. {stemtrace-0.3.5 → stemtrace-0.3.6}/LICENSE +0 -0
  69. {stemtrace-0.3.5 → stemtrace-0.3.6}/Makefile +0 -0
  70. {stemtrace-0.3.5 → stemtrace-0.3.6}/docker-compose.e2e.rabbitmq.yml +0 -0
  71. {stemtrace-0.3.5 → stemtrace-0.3.6}/docker-compose.e2e.yml +0 -0
  72. {stemtrace-0.3.5 → stemtrace-0.3.6}/docker-compose.rabbitmq.yml +0 -0
  73. {stemtrace-0.3.5 → stemtrace-0.3.6}/docker-compose.yml +0 -0
  74. {stemtrace-0.3.5 → stemtrace-0.3.6}/docs/screenshots/task_details.png +0 -0
  75. {stemtrace-0.3.5 → stemtrace-0.3.6}/docs/screenshots/unregistered.png +0 -0
  76. {stemtrace-0.3.5 → stemtrace-0.3.6}/docs/screenshots/workflow.png +0 -0
  77. {stemtrace-0.3.5 → stemtrace-0.3.6}/examples/celery_app.py +0 -0
  78. {stemtrace-0.3.5 → stemtrace-0.3.6}/progress.md +0 -0
  79. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/core/__init__.py +0 -0
  80. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/core/events.py +0 -0
  81. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/core/exceptions.py +0 -0
  82. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/core/graph.py +0 -0
  83. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/__init__.py +0 -0
  84. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/bootsteps.py +0 -0
  85. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/config.py +0 -0
  86. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/scrubbing.py +0 -0
  87. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/signals.py +0 -0
  88. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/transports/__init__.py +0 -0
  89. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/transports/memory.py +0 -0
  90. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/library/transports/rabbitmq.py +0 -0
  91. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/py.typed +0 -0
  92. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/__init__.py +0 -0
  93. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/__main__.py +0 -0
  94. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/api/__init__.py +0 -0
  95. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/api/websocket.py +0 -0
  96. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/fastapi/__init__.py +0 -0
  97. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/fastapi/auth.py +0 -0
  98. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/fastapi/extension.py +0 -0
  99. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/fastapi/form_auth.py +0 -0
  100. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/fastapi/login_routes.py +0 -0
  101. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/fastapi/router.py +0 -0
  102. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/store.py +0 -0
  103. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/__init__.py +0 -0
  104. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/playwright.config.ts +0 -0
  105. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/postcss.config.js +0 -0
  106. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/api/queries.ts +0 -0
  107. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/DateRangePicker.tsx +0 -0
  108. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/Filters.tsx +0 -0
  109. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/TaskGraph.tsx +0 -0
  110. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/TaskList.tsx +0 -0
  111. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/TaskStateBadge.tsx +0 -0
  112. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/TaskTimeline.tsx +0 -0
  113. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/components/Tooltip.tsx +0 -0
  114. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/hooks/WebSocketContext.tsx +0 -0
  115. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/hooks/useInfiniteScroll.ts +0 -0
  116. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/index.css +0 -0
  117. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/main.tsx +0 -0
  118. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/graph.$rootId.tsx +0 -0
  119. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/graphs.tsx +0 -0
  120. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/index.tsx +0 -0
  121. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/registry.tsx +0 -0
  122. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/tasks.$taskId.tsx +0 -0
  123. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/routes/workers.tsx +0 -0
  124. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/utils/format.ts +0 -0
  125. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/src/vite-env.d.ts +0 -0
  126. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-api.ts +0 -0
  127. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/fixtures/mock-data.ts +0 -0
  128. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/graphs.spec.ts +0 -0
  129. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/registry.spec.ts +0 -0
  130. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/task-detail.spec.ts +0 -0
  131. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/tasks.spec.ts +0 -0
  132. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tests/workers.spec.ts +0 -0
  133. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tsconfig.json +0 -0
  134. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/tsconfig.tsbuildinfo +0 -0
  135. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/ui/frontend/vite.config.ts +0 -0
  136. {stemtrace-0.3.5 → stemtrace-0.3.6}/src/stemtrace/server/websocket.py +0 -0
  137. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/__init__.py +0 -0
  138. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/e2e/__init__.py +0 -0
  139. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/e2e/conftest.py +0 -0
  140. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/e2e/tasks.py +0 -0
  141. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/e2e/test_api_e2e.py +0 -0
  142. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/e2e/test_workers_e2e.py +0 -0
  143. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/__init__.py +0 -0
  144. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/integration/test_embedded_static.py +0 -0
  145. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/__init__.py +0 -0
  146. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_auth.py +0 -0
  147. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_bootsteps.py +0 -0
  148. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_config.py +0 -0
  149. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_exceptions.py +0 -0
  150. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_form_auth.py +0 -0
  151. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_init.py +0 -0
  152. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_scrubbing.py +0 -0
  153. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_signals.py +0 -0
  154. {stemtrace-0.3.5 → stemtrace-0.3.6}/tests/unit/test_ui_static_prefix.py +0 -0
@@ -1,5 +1,5 @@
1
1
  [tool.bumpversion]
2
- current_version = "0.3.5"
2
+ current_version = "0.3.6"
3
3
  parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
4
4
  serialize = ["{major}.{minor}.{patch}"]
5
5
  commit = true
@@ -1,6 +1,7 @@
1
1
  version: 2
2
2
  updates:
3
- - package-ecosystem: pip
3
+ # Python dependencies: pyproject.toml + uv.lock (CI and images install from the lock)
4
+ - package-ecosystem: uv
4
5
  directory: /
5
6
  schedule:
6
7
  interval: weekly
@@ -20,3 +21,11 @@ updates:
20
21
  interval: weekly
21
22
  commit-message:
22
23
  prefix: "chore(deps)"
24
+
25
+ # Base images in Dockerfile and Dockerfile.e2e (tags pinned by digest)
26
+ - package-ecosystem: docker
27
+ directory: /
28
+ schedule:
29
+ interval: weekly
30
+ commit-message:
31
+ prefix: "chore(deps)"
@@ -0,0 +1,230 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ push:
7
+ branches: [main]
8
+ workflow_dispatch:
9
+
10
+ concurrency:
11
+ group: ${{ github.workflow }}-${{ github.ref }}
12
+ cancel-in-progress: true
13
+
14
+ jobs:
15
+ lint:
16
+ if: github.event_name == 'pull_request'
17
+ runs-on: ubuntu-latest
18
+ env:
19
+ UV_PYTHON: "3.12"
20
+ STEMTRACE_SKIP_UI_BUILD: "1"
21
+ steps:
22
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
23
+
24
+ - name: Set up Python
25
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
26
+ with:
27
+ python-version: "3.12"
28
+
29
+ - name: Install uv
30
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
31
+ with:
32
+ enable-cache: true
33
+
34
+ - name: Install dependencies
35
+ run: uv sync --locked --no-build --all-extras
36
+
37
+ - name: Lint with ruff
38
+ run: uv run --locked --no-build ruff check src/ tests/
39
+
40
+ - name: Check formatting
41
+ run: uv run --locked --no-build ruff format --check src/ tests/
42
+
43
+ type-check:
44
+ if: github.event_name == 'pull_request'
45
+ runs-on: ubuntu-latest
46
+ env:
47
+ UV_PYTHON: "3.12"
48
+ STEMTRACE_SKIP_UI_BUILD: "1"
49
+ steps:
50
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
51
+
52
+ - name: Set up Python
53
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
54
+ with:
55
+ python-version: "3.12"
56
+
57
+ - name: Install uv
58
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
59
+ with:
60
+ enable-cache: true
61
+
62
+ - name: Install dependencies
63
+ run: uv sync --locked --no-build --all-extras
64
+
65
+ - name: Type check with mypy
66
+ run: uv run --locked --no-build mypy src/ --strict
67
+
68
+ test:
69
+ if: github.event_name == 'pull_request'
70
+ runs-on: ubuntu-latest
71
+ env:
72
+ UV_PYTHON: ${{ matrix.python-version }}
73
+ STEMTRACE_SKIP_UI_BUILD: "1"
74
+ strategy:
75
+ matrix:
76
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
77
+ steps:
78
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
79
+
80
+ - name: Set up Python ${{ matrix.python-version }}
81
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
82
+ with:
83
+ python-version: ${{ matrix.python-version }}
84
+
85
+ - name: Install uv
86
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
87
+ with:
88
+ enable-cache: true
89
+
90
+ - name: Install dependencies
91
+ run: uv sync --locked --no-build --all-extras
92
+
93
+ - name: Run tests
94
+ run: uv run --locked --no-build pytest --cov=stemtrace --cov-branch --cov-report=xml --cov-fail-under=95
95
+
96
+ - name: Upload coverage
97
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
98
+ if: matrix.python-version == '3.12'
99
+ with:
100
+ token: ${{ secrets.CODECOV_TOKEN }}
101
+ files: ./coverage.xml
102
+ fail_ci_if_error: false
103
+
104
+ audit:
105
+ name: Audit runtime dependencies
106
+ runs-on: ubuntu-latest
107
+ env:
108
+ UV_PYTHON: "3.12"
109
+ steps:
110
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
111
+
112
+ - name: Set up Python
113
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
114
+ with:
115
+ python-version: "3.12"
116
+
117
+ - name: Install uv
118
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
119
+ with:
120
+ enable-cache: true
121
+
122
+ - name: Export locked runtime dependencies
123
+ run: uv export --frozen --no-emit-project --no-dev --output-file requirements-audit.txt
124
+
125
+ - name: Audit for known vulnerabilities
126
+ run: uvx --no-build pip-audit==2.10.1 --strict --require-hashes --disable-pip -r requirements-audit.txt
127
+
128
+ frontend:
129
+ if: github.event_name == 'pull_request'
130
+ runs-on: ubuntu-latest
131
+ defaults:
132
+ run:
133
+ working-directory: src/stemtrace/server/ui/frontend
134
+ steps:
135
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
136
+
137
+ - name: Set up Node.js
138
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
139
+ with:
140
+ node-version: "22"
141
+ cache: "npm"
142
+ cache-dependency-path: src/stemtrace/server/ui/frontend/package-lock.json
143
+
144
+ - name: Install dependencies
145
+ run: npm ci --ignore-scripts
146
+
147
+ - name: Lint with Biome
148
+ run: npm run check
149
+
150
+ - name: Type check with TypeScript
151
+ run: npm run typecheck
152
+
153
+ build:
154
+ if: github.event_name == 'pull_request'
155
+ runs-on: ubuntu-latest
156
+ env:
157
+ UV_PYTHON: "3.12"
158
+ needs: [lint, type-check, test, frontend]
159
+ steps:
160
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
161
+
162
+ - name: Set up Python
163
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
164
+ with:
165
+ python-version: "3.12"
166
+
167
+ - name: Install uv
168
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
169
+ with:
170
+ enable-cache: true
171
+
172
+ - name: Set up Node.js
173
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
174
+ with:
175
+ node-version: "22"
176
+ cache: "npm"
177
+ cache-dependency-path: src/stemtrace/server/ui/frontend/package-lock.json
178
+
179
+ - name: Install frontend dependencies
180
+ run: npm ci --ignore-scripts
181
+ working-directory: src/stemtrace/server/ui/frontend
182
+
183
+ - name: Install build tools
184
+ run: uv sync --locked --no-build --all-extras --no-install-project
185
+
186
+ - name: Build package
187
+ run: uv run --locked --no-build --no-sync pyproject-build --no-isolation
188
+
189
+ - name: Verify package
190
+ run: uv run --locked --no-build --no-sync twine check dist/*
191
+
192
+ - name: Upload dist artifacts
193
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
194
+ with:
195
+ name: dist
196
+ path: dist/
197
+
198
+ coverage-main:
199
+ # Keep PR checks as the main gate, but ensure Codecov gets updated for the
200
+ # actual default-branch commit after merges (or direct pushes / releases).
201
+ if: github.ref == 'refs/heads/main' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
202
+ runs-on: ubuntu-latest
203
+ env:
204
+ UV_PYTHON: "3.12"
205
+ STEMTRACE_SKIP_UI_BUILD: "1"
206
+ steps:
207
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
208
+
209
+ - name: Set up Python
210
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
211
+ with:
212
+ python-version: "3.12"
213
+
214
+ - name: Install uv
215
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
216
+ with:
217
+ enable-cache: true
218
+
219
+ - name: Install dependencies
220
+ run: uv sync --locked --no-build --all-extras
221
+
222
+ - name: Run tests (coverage only)
223
+ run: uv run --locked --no-build pytest --cov=stemtrace --cov-branch --cov-report=xml --cov-fail-under=95
224
+
225
+ - name: Upload coverage (main)
226
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
227
+ with:
228
+ token: ${{ secrets.CODECOV_TOKEN }}
229
+ files: ./coverage.xml
230
+ fail_ci_if_error: false
@@ -12,6 +12,10 @@ jobs:
12
12
  api-e2e:
13
13
  name: API E2E Tests
14
14
  runs-on: ubuntu-latest
15
+ env:
16
+ UV_PYTHON: "3.12"
17
+ # The Docker images build their own UI; skip the host-side build.
18
+ STEMTRACE_SKIP_UI_BUILD: "1"
15
19
  timeout-minutes: 15
16
20
  strategy:
17
21
  fail-fast: false
@@ -21,10 +25,10 @@ jobs:
21
25
  - rabbitmq
22
26
 
23
27
  steps:
24
- - uses: actions/checkout@v6
28
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
25
29
 
26
30
  - name: Set up Docker Buildx
27
- uses: docker/setup-buildx-action@v4
31
+ uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
28
32
 
29
33
  - name: Select compose file and Celery env
30
34
  run: |
@@ -51,20 +55,20 @@ jobs:
51
55
  echo "Services ready!"
52
56
 
53
57
  - name: Set up Python
54
- uses: actions/setup-python@v6
58
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
55
59
  with:
56
60
  python-version: "3.12"
57
61
 
58
62
  - name: Install uv
59
- uses: astral-sh/setup-uv@v7
63
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
60
64
  with:
61
65
  enable-cache: true
62
66
 
63
67
  - name: Install dependencies
64
- run: uv sync --all-extras
68
+ run: uv sync --locked --no-build --all-extras
65
69
 
66
70
  - name: Run API E2E tests
67
- run: uv run pytest tests/e2e/ -m e2e -v --tb=short
71
+ run: uv run --locked --no-build pytest tests/e2e/ -m e2e -v --tb=short
68
72
 
69
73
  - name: Show logs on failure
70
74
  if: failure()
@@ -79,6 +83,10 @@ jobs:
79
83
  playwright:
80
84
  name: Playwright E2E Tests
81
85
  runs-on: ubuntu-latest
86
+ env:
87
+ UV_PYTHON: "3.12"
88
+ # The Docker images build their own UI; skip the host-side build.
89
+ STEMTRACE_SKIP_UI_BUILD: "1"
82
90
  timeout-minutes: 20
83
91
  strategy:
84
92
  fail-fast: false
@@ -88,10 +96,10 @@ jobs:
88
96
  - rabbitmq
89
97
 
90
98
  steps:
91
- - uses: actions/checkout@v6
99
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
92
100
 
93
101
  - name: Set up Docker Buildx
94
- uses: docker/setup-buildx-action@v4
102
+ uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
95
103
 
96
104
  - name: Select compose file and Celery env
97
105
  run: |
@@ -117,22 +125,22 @@ jobs:
117
125
  echo "Services ready!"
118
126
 
119
127
  - name: Set up Python
120
- uses: actions/setup-python@v6
128
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
121
129
  with:
122
130
  python-version: "3.12"
123
131
 
124
132
  - name: Install uv
125
- uses: astral-sh/setup-uv@v7
133
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
126
134
  with:
127
135
  enable-cache: true
128
136
 
129
137
  - name: Install Python dependencies
130
- run: uv sync --all-extras
138
+ run: uv sync --locked --no-build --all-extras
131
139
 
132
140
  - name: Submit test tasks
133
141
  run: |
134
142
  # Submit some tasks to populate the UI for testing
135
- uv run python -c "
143
+ uv run --locked --no-build python -c "
136
144
  from tests.e2e.tasks import add, create_workflow, always_fails
137
145
  # Submit a few tasks
138
146
  add.delay(1, 2)
@@ -145,19 +153,19 @@ jobs:
145
153
  sleep 5
146
154
 
147
155
  - name: Set up Node.js
148
- uses: actions/setup-node@v6
156
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
149
157
  with:
150
- node-version: "20"
158
+ node-version: "22"
151
159
  cache: "npm"
152
160
  cache-dependency-path: src/stemtrace/server/ui/frontend/package-lock.json
153
161
 
154
162
  - name: Install frontend dependencies
155
163
  working-directory: src/stemtrace/server/ui/frontend
156
- run: npm ci
164
+ run: npm ci --ignore-scripts
157
165
 
158
166
  - name: Install Playwright browsers
159
167
  working-directory: src/stemtrace/server/ui/frontend
160
- run: npx playwright install chromium --with-deps
168
+ run: ./node_modules/.bin/playwright install chromium --with-deps
161
169
 
162
170
  - name: Run Playwright tests
163
171
  working-directory: src/stemtrace/server/ui/frontend
@@ -167,7 +175,7 @@ jobs:
167
175
  PLAYWRIGHT_BASE_URL: http://localhost:8000
168
176
 
169
177
  - name: Upload Playwright report
170
- uses: actions/upload-artifact@v7
178
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
171
179
  if: always()
172
180
  with:
173
181
  name: playwright-report-${{ matrix.broker }}
@@ -175,7 +183,7 @@ jobs:
175
183
  retention-days: 7
176
184
 
177
185
  - name: Upload test results
178
- uses: actions/upload-artifact@v7
186
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
179
187
  if: failure()
180
188
  with:
181
189
  name: playwright-test-results-${{ matrix.broker }}
@@ -18,11 +18,13 @@ env:
18
18
  jobs:
19
19
  build:
20
20
  runs-on: ubuntu-latest
21
+ env:
22
+ UV_PYTHON: "3.12"
21
23
  outputs:
22
24
  version: ${{ steps.version.outputs.version }}
23
25
  tag: ${{ steps.version.outputs.tag }}
24
26
  steps:
25
- - uses: actions/checkout@v6
27
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
26
28
 
27
29
  - name: Determine version
28
30
  id: version
@@ -49,32 +51,37 @@ jobs:
49
51
  echo "✅ Version $PYPROJECT_VERSION matches"
50
52
 
51
53
  - name: Set up Python
52
- uses: actions/setup-python@v6
54
+ uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
53
55
  with:
54
56
  python-version: "3.12"
55
57
 
56
58
  - name: Set up Node.js
57
- uses: actions/setup-node@v6
59
+ uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
58
60
  with:
59
- node-version: "20"
61
+ node-version: "22"
60
62
  cache: "npm"
61
63
  cache-dependency-path: src/stemtrace/server/ui/frontend/package-lock.json
62
64
 
65
+ - name: Install uv
66
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
67
+ with:
68
+ enable-cache: false
69
+
63
70
  - name: Install frontend dependencies
64
- run: npm ci
71
+ run: npm ci --ignore-scripts
65
72
  working-directory: src/stemtrace/server/ui/frontend
66
73
 
67
74
  - name: Install build tools
68
- run: pip install build twine
75
+ run: uv sync --locked --no-build --all-extras --no-install-project
69
76
 
70
77
  - name: Build package
71
- run: python -m build
78
+ run: uv run --locked --no-build --no-sync pyproject-build --no-isolation
72
79
 
73
80
  - name: Verify package
74
- run: twine check dist/*
81
+ run: uv run --locked --no-build --no-sync twine check dist/*
75
82
 
76
83
  - name: Upload dist artifacts
77
- uses: actions/upload-artifact@v7
84
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
78
85
  with:
79
86
  name: dist
80
87
  path: dist/
@@ -87,13 +94,13 @@ jobs:
87
94
  id-token: write
88
95
  steps:
89
96
  - name: Download dist artifacts
90
- uses: actions/download-artifact@v8
97
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
91
98
  with:
92
99
  name: dist
93
100
  path: dist/
94
101
 
95
102
  - name: Publish to PyPI
96
- uses: pypa/gh-action-pypi-publish@release/v1
103
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
97
104
 
98
105
  docker:
99
106
  needs: build
@@ -102,16 +109,16 @@ jobs:
102
109
  contents: read
103
110
  packages: write
104
111
  steps:
105
- - uses: actions/checkout@v6
112
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
106
113
 
107
114
  - name: Set up QEMU
108
- uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
115
+ uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
109
116
 
110
117
  - name: Set up Docker Buildx
111
- uses: docker/setup-buildx-action@v4
118
+ uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
112
119
 
113
120
  - name: Log in to GitHub Container Registry
114
- uses: docker/login-action@v3
121
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
115
122
  with:
116
123
  registry: ghcr.io
117
124
  username: ${{ github.actor }}
@@ -119,7 +126,7 @@ jobs:
119
126
 
120
127
  - name: Extract metadata
121
128
  id: meta
122
- uses: docker/metadata-action@v6
129
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
123
130
  with:
124
131
  images: ghcr.io/${{ github.repository }}
125
132
  tags: |
@@ -127,7 +134,7 @@ jobs:
127
134
  type=raw,value=latest
128
135
 
129
136
  - name: Build and push Docker image
130
- uses: docker/build-push-action@v7
137
+ uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
131
138
  with:
132
139
  context: .
133
140
  push: true
@@ -143,10 +150,10 @@ jobs:
143
150
  permissions:
144
151
  contents: write
145
152
  steps:
146
- - uses: actions/checkout@v6
153
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
147
154
 
148
155
  - name: Download dist artifacts
149
- uses: actions/download-artifact@v8
156
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
150
157
  with:
151
158
  name: dist
152
159
  path: dist/
@@ -160,7 +167,7 @@ jobs:
160
167
  git push origin "${{ needs.build.outputs.tag }}"
161
168
 
162
169
  - name: Create GitHub Release
163
- uses: softprops/action-gh-release@v2
170
+ uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
164
171
  with:
165
172
  tag_name: ${{ needs.build.outputs.tag }}
166
173
  generate_release_notes: true
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.6] - 2026-10-10
11
+
12
+ ### Security
13
+ - Updated UI bundle dependencies with known advisories (seroval, TanStack Router). The runtime (shipped) dependencies that changed are `@tanstack/react-router` 1.168.1 → 1.170.41, with its `@tanstack/router-core` 1.168.1 → 1.171.34, `@tanstack/history` 1.161.6 → 1.162.4, `@tanstack/react-store`/`@tanstack/store` 0.9.2 → 0.11.2 and `cookie-es` 2.0.0 → 3.1.1; and `seroval`/`seroval-plugins` 1.4.2 → 1.6.8, which fixes GHSA-3rxj-6cgf-8cfw, GHSA-hj76-42vx-jwp4, GHSA-mv8w-475r-vwqw, GHSA-p6vx-979v-rg4c and related denial-of-service advisories. seroval is only used by the router's server-side rendering code, which stemtrace doesn't use, and none of it ends up in the built UI bundle. `@tanstack/react-query` also moved 5.90.16 → 5.94.5 (no advisory). Build-time-only packages (Vite, Rollup, PostCSS and others) were updated too.
14
+
15
+ ### Added
16
+ - Health endpoint: `consumer_connected` reports whether the event consumer's last broker read succeeded (`true`), is failing (`false`), or is unknown (`null`). Only the Redis transport reports it: it detects refused or reset connections, connect timeouts and repeated read timeouts. On redis-py < 8, whose default socket timeout is unbounded, a half-open or hung connection may go unnoticed unless the broker URL sets `socket_timeout` or `health_check_interval`. With RabbitMQ it is always `null`.
17
+ - UI: "Sprout" brand mark in the header and as the favicon (dashboard and login page). The favicon is an inline SVG data URI that follows the OS light/dark theme, so it needs no extra request, is never redirected by form login, and works under any mount prefix. Brand assets live in `docs/brand/`.
18
+
19
+ ### Changed
20
+ - Minimum FastAPI is now 0.112.1 and minimum Starlette 0.38.6: the lowest versions verified by the test suite at release time. FastAPI 0.100–0.104 never worked with current Pydantic (building the app fails with `'FieldInfo' object has no attribute 'in_'`). Starlette is now a declared dependency because stemtrace imports it directly.
21
+ - Minimum redis-py is now 4.1.0, the lowest version verified by the test suite at release time; 4.0.x imports `distutils`, which was removed in Python 3.12.
22
+ - Docker image: Python dependencies are now installed from the project's lockfile, so each image ships the exact versions audited for that release instead of whatever was newest at build time. Base images are pinned by digest, and the UI is built with Node 22.
23
+ - Examples now bind to `127.0.0.1` instead of `0.0.0.0`.
24
+ - Docker image: stemtrace now runs from a virtual environment at `/opt/venv` (it's first on `PATH`), and the base image's unused system `pip` is removed. To add packages in a derived image, use `RUN python -m ensurepip && python -m pip install <package>`, which installs into `/opt/venv`.
25
+ - Docker image: the built-in `HEALTHCHECK` only checks the HTTP status, so a container whose event consumer is degraded (e.g. the broker is unreachable) still reports healthy. Probe `/stemtrace/api/health` and check its `status` field if you need to alert on that.
26
+ - Building from source: the UI build hook now runs `npm ci --ignore-scripts` (or `npm install --ignore-scripts` without a lockfile) instead of `npm install`. Set `STEMTRACE_SKIP_UI_BUILD=1` to skip the UI build entirely; a wheel built that way contains no UI.
27
+ - Health endpoint: `status` is now `"degraded"` instead of `"ok"` when the event consumer died unexpectedly, its loop keeps restarting after errors (such as `WRONGTYPE` or `NOPERM`), or `consumer_connected` is `false`. With RabbitMQ only consumer loop failures are detected. A consumer stopped on purpose, or whose event source finished normally (for example `memory://`), is not degraded. The response is still HTTP 200, so status-code-only health checks behave as before; check the `status` field to alert on a degraded consumer. See the README's "Health Endpoint" section.
28
+
29
+ ### Fixed
30
+ - Redis transport: the server stopped receiving events after about 5 seconds of quiet when used with redis-py 8. redis-py 8 defaults `socket_timeout` to 5 seconds, the same as the `XREAD` block window, so the first idle read timed out and the consumer thread exited for good. The `XREAD` block window is now half of the client's effective socket timeout (2.5 seconds with redis-py 8 defaults, 5 seconds when the timeout is unbounded), so idle reads return normally on a single connection. This includes clients passed in directly, such as `redis.Redis.from_url(url)`. The client's own timeouts are unchanged, so publishing from tasks never blocks longer than before.
31
+ - Redis transport: losing the connection (for example a Redis restart, an unreachable host, or a connect timeout) no longer stops event consumption. The consumer logs one warning with a traceback, then one line per attempt, and retries with exponential backoff (1 s doubling to a 30 s cap, reset on success), resuming from the last stream entry it read. Repeated read timeouts are logged as warnings at most once a minute.
32
+ - Server: the event consumer thread restarts with the same backoff after an unexpected error instead of exiting silently, and `stop()` interrupts a pending reconnect wait instead of leaving the thread behind.
33
+ - UI: the favicon no longer 404s (the dashboard referenced a non-existent `/vite.svg`).
34
+ - UI: JS and CSS assets no longer 404 on FastAPI 0.137+ when stemtrace is installed with per-file symlinks (e.g. `uv pip install --link-mode symlink`). The `/assets` mount now follows symlinks inside the bundle; request paths are still confined to the assets directory.
35
+
10
36
  ## [0.3.5] - 2026-10-09
11
37
 
12
38
  ### Security
@@ -122,7 +148,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
122
148
  - **E2E test suite**: Docker API tests + Playwright browser tests
123
149
  - **Comprehensive test suite**: 350+ Python tests, 90%+ coverage
124
150
 
125
- [unreleased]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.0...HEAD
151
+ [unreleased]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.5...HEAD
152
+ [0.3.5]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.4...v0.3.5
153
+ [0.3.4]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.3...v0.3.4
154
+ [0.3.3]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.2...v0.3.3
155
+ [0.3.2]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.1...v0.3.2
156
+ [0.3.1]: https://github.com/iansokolskyi/stemtrace/compare/v0.3.0...v0.3.1
126
157
  [0.3.0]: https://github.com/iansokolskyi/stemtrace/compare/v0.2.2...v0.3.0
127
158
  [0.2.2]: https://github.com/iansokolskyi/stemtrace/compare/v0.2.1...v0.2.2
128
159
  [0.2.1]: https://github.com/iansokolskyi/stemtrace/compare/v0.2.0...v0.2.1
@@ -0,0 +1,69 @@
1
+ # stemtrace server Dockerfile
2
+ # Multi-stage build for smaller production image
3
+
4
+ # Stage 1: Build frontend
5
+ FROM node:22.23.3-alpine3.24@sha256:0a7108bf6c7bf5de370ffb1a3ed6be93d405b43ff159f681a8d18c0e2bc2e402 AS frontend-builder
6
+
7
+ WORKDIR /app/frontend
8
+ COPY src/stemtrace/server/ui/frontend/package*.json ./
9
+ RUN npm ci --ignore-scripts
10
+
11
+ COPY src/stemtrace/server/ui/frontend/ ./
12
+ RUN npm run build
13
+
14
+ # Stage 2: Build a virtualenv with the locked dependencies and stemtrace itself
15
+ FROM python:3.12.14-slim-trixie@sha256:f77ac9e44ae96ef2c90b8053ea08c31f8be030f824196b0ae4db6d462c84e51f AS builder
16
+
17
+ COPY --from=ghcr.io/astral-sh/uv:0.12.18@sha256:3adc3706091ce7c2fe595e669628caedd6d951551b92b258b7e7dbe06d9440bc /uv /usr/local/bin/uv
18
+ ENV UV_PYTHON_DOWNLOADS=never \
19
+ UV_PROJECT_ENVIRONMENT=/opt/venv \
20
+ UV_NO_CACHE=1
21
+
22
+ WORKDIR /app
23
+
24
+ # Install locked runtime dependencies from wheels only (cached layer)
25
+ COPY pyproject.toml uv.lock ./
26
+ RUN uv sync --locked --no-build --no-install-project && rm -f /opt/venv/.lock
27
+
28
+ # Copy source
29
+ COPY README.md LICENSE build_ui.py ./
30
+ COPY src/ src/
31
+
32
+ # Copy pre-built frontend
33
+ COPY --from=frontend-builder /app/frontend/dist/ src/stemtrace/server/ui/frontend/dist/
34
+
35
+ # Build and install stemtrace itself (non-editable) into the virtualenv
36
+ RUN uv sync --locked --no-build --no-editable && rm -f /opt/venv/.lock
37
+
38
+ # Stage 3: Production image
39
+ FROM python:3.12.14-slim-trixie@sha256:f77ac9e44ae96ef2c90b8053ea08c31f8be030f824196b0ae4db6d462c84e51f AS production
40
+
41
+ WORKDIR /app
42
+
43
+ COPY --from=builder /opt/venv /opt/venv
44
+ ENV PATH="/opt/venv/bin:$PATH"
45
+
46
+ # Drop the base image's system pip: stemtrace runs from /opt/venv and never
47
+ # uses it, and it would otherwise ship known advisories. `python -m ensurepip`
48
+ # still works for derived images (see CHANGELOG).
49
+ RUN /usr/local/bin/python -m pip uninstall --yes --quiet pip \
50
+ && rm -f /usr/local/bin/pip /usr/local/bin/pip3 /usr/local/bin/pip3.12
51
+
52
+ # Create non-root user
53
+ RUN useradd -m -u 1000 stemtrace
54
+ USER stemtrace
55
+
56
+ # Default environment
57
+ ENV STEMTRACE_BROKER_URL=""
58
+ ENV HOST="0.0.0.0"
59
+ ENV PORT="8000"
60
+
61
+ EXPOSE 8000
62
+
63
+ # Health check (CLI server mounts API at /stemtrace prefix)
64
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
65
+ CMD python -c "import httpx; httpx.get('http://localhost:8000/stemtrace/api/health').raise_for_status()"
66
+
67
+ # Default command: run server
68
+ ENTRYPOINT ["stemtrace"]
69
+ CMD ["server", "--host", "0.0.0.0", "--port", "8000"]