blackboardxray 0.1.0__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 (151) hide show
  1. blackboardxray-0.1.0/.editorconfig +22 -0
  2. blackboardxray-0.1.0/.gitattributes +20 -0
  3. blackboardxray-0.1.0/.github/ISSUE_TEMPLATE/bug.yml +80 -0
  4. blackboardxray-0.1.0/.github/ISSUE_TEMPLATE/config.yml +16 -0
  5. blackboardxray-0.1.0/.github/ISSUE_TEMPLATE/feature.yml +46 -0
  6. blackboardxray-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  7. blackboardxray-0.1.0/.github/dependabot.yml +44 -0
  8. blackboardxray-0.1.0/.github/workflows/ci.yml +136 -0
  9. blackboardxray-0.1.0/.github/workflows/release.yml +167 -0
  10. blackboardxray-0.1.0/.github/workflows/security.yml +92 -0
  11. blackboardxray-0.1.0/.github/workflows/title-lint.yml +79 -0
  12. blackboardxray-0.1.0/.gitignore +26 -0
  13. blackboardxray-0.1.0/.pre-commit-config.yaml +49 -0
  14. blackboardxray-0.1.0/.release-please-manifest.json +3 -0
  15. blackboardxray-0.1.0/CHANGELOG.md +34 -0
  16. blackboardxray-0.1.0/CODE_OF_CONDUCT.md +129 -0
  17. blackboardxray-0.1.0/CONTRIBUTING.md +101 -0
  18. blackboardxray-0.1.0/Dockerfile +32 -0
  19. blackboardxray-0.1.0/LICENSE +202 -0
  20. blackboardxray-0.1.0/Makefile +45 -0
  21. blackboardxray-0.1.0/PKG-INFO +232 -0
  22. blackboardxray-0.1.0/PLAN.md +273 -0
  23. blackboardxray-0.1.0/README.md +207 -0
  24. blackboardxray-0.1.0/SECURITY.md +102 -0
  25. blackboardxray-0.1.0/docker-compose.yml +75 -0
  26. blackboardxray-0.1.0/docs/adr/0001-scope.md +49 -0
  27. blackboardxray-0.1.0/docs/adr/0002-one-database.md +42 -0
  28. blackboardxray-0.1.0/docs/adr/0003-sessions-not-tokens.md +40 -0
  29. blackboardxray-0.1.0/docs/adr/0004-invite-links-not-email.md +31 -0
  30. blackboardxray-0.1.0/docs/architecture.md +107 -0
  31. blackboardxray-0.1.0/docs/design/brand.md +87 -0
  32. blackboardxray-0.1.0/docs/design/objects.md +151 -0
  33. blackboardxray-0.1.0/docs/design/rules.md +127 -0
  34. blackboardxray-0.1.0/docs/design-system.md +284 -0
  35. blackboardxray-0.1.0/docs/limits.md +92 -0
  36. blackboardxray-0.1.0/docs/product.md +89 -0
  37. blackboardxray-0.1.0/docs/self-hosting.md +192 -0
  38. blackboardxray-0.1.0/examples/backfill.py +242 -0
  39. blackboardxray-0.1.0/examples/seed.py +248 -0
  40. blackboardxray-0.1.0/pyproject.toml +96 -0
  41. blackboardxray-0.1.0/release-please-config.json +23 -0
  42. blackboardxray-0.1.0/src/blackboardxray/__init__.py +58 -0
  43. blackboardxray-0.1.0/src/blackboardxray/_transport.py +381 -0
  44. blackboardxray-0.1.0/src/blackboardxray/events.py +223 -0
  45. blackboardxray-0.1.0/src/blackboardxray/observe.py +671 -0
  46. blackboardxray-0.1.0/src/blackboardxray/py.typed +0 -0
  47. blackboardxray-0.1.0/src/blackboardxray/server/__init__.py +0 -0
  48. blackboardxray-0.1.0/src/blackboardxray/server/__main__.py +123 -0
  49. blackboardxray-0.1.0/src/blackboardxray/server/app.py +261 -0
  50. blackboardxray-0.1.0/src/blackboardxray/server/db.py +672 -0
  51. blackboardxray-0.1.0/src/blackboardxray/server/identity.py +180 -0
  52. blackboardxray-0.1.0/src/blackboardxray/server/limits.py +88 -0
  53. blackboardxray-0.1.0/src/blackboardxray/server/migrate.py +184 -0
  54. blackboardxray-0.1.0/src/blackboardxray/server/migrations/0001_initial.sql +101 -0
  55. blackboardxray-0.1.0/src/blackboardxray/server/migrations/0002_people_and_organizations.sql +155 -0
  56. blackboardxray-0.1.0/src/blackboardxray/server/migrations/0003_audit.sql +27 -0
  57. blackboardxray-0.1.0/src/blackboardxray/server/people.py +811 -0
  58. blackboardxray-0.1.0/src/blackboardxray/server/provision.py +113 -0
  59. blackboardxray-0.1.0/src/blackboardxray/server/roles.py +140 -0
  60. blackboardxray-0.1.0/src/blackboardxray/server/routes/__init__.py +0 -0
  61. blackboardxray-0.1.0/src/blackboardxray/server/routes/admin.py +378 -0
  62. blackboardxray-0.1.0/src/blackboardxray/server/routes/auth.py +345 -0
  63. blackboardxray-0.1.0/src/blackboardxray/server/routes/data.py +215 -0
  64. blackboardxray-0.1.0/src/blackboardxray/server/security.py +218 -0
  65. blackboardxray-0.1.0/src/blackboardxray/server/settings.py +123 -0
  66. blackboardxray-0.1.0/src/blackboardxray/server/upkeep.py +155 -0
  67. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  68. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  69. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  70. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  71. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  72. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  73. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  74. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  75. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  76. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  77. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  78. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/index-BJnByUnU.css +1 -0
  79. blackboardxray-0.1.0/src/blackboardxray/server/web/assets/index-Dp4Jd2bK.js +217 -0
  80. blackboardxray-0.1.0/src/blackboardxray/server/web/index.html +28 -0
  81. blackboardxray-0.1.0/tests/conftest.py +48 -0
  82. blackboardxray-0.1.0/tests/test_access.py +286 -0
  83. blackboardxray-0.1.0/tests/test_events.py +116 -0
  84. blackboardxray-0.1.0/tests/test_identity.py +193 -0
  85. blackboardxray-0.1.0/tests/test_migrate.py +308 -0
  86. blackboardxray-0.1.0/tests/test_observe.py +489 -0
  87. blackboardxray-0.1.0/tests/test_onboarding.py +233 -0
  88. blackboardxray-0.1.0/tests/test_people.py +384 -0
  89. blackboardxray-0.1.0/tests/test_server.py +573 -0
  90. blackboardxray-0.1.0/tests/test_transport.py +171 -0
  91. blackboardxray-0.1.0/tests/test_upkeep.py +242 -0
  92. blackboardxray-0.1.0/tests/test_wire_mirror.py +53 -0
  93. blackboardxray-0.1.0/ui/index.html +27 -0
  94. blackboardxray-0.1.0/ui/package-lock.json +3510 -0
  95. blackboardxray-0.1.0/ui/package.json +43 -0
  96. blackboardxray-0.1.0/ui/scripts/build-tokens.mjs +318 -0
  97. blackboardxray-0.1.0/ui/scripts/check-contrast.mjs +167 -0
  98. blackboardxray-0.1.0/ui/scripts/check-raw-values.mjs +76 -0
  99. blackboardxray-0.1.0/ui/shots.mjs +58 -0
  100. blackboardxray-0.1.0/ui/src/App.tsx +112 -0
  101. blackboardxray-0.1.0/ui/src/components/BoardContents.tsx +165 -0
  102. blackboardxray-0.1.0/ui/src/components/BoardTable.tsx +211 -0
  103. blackboardxray-0.1.0/ui/src/components/Dot.tsx +42 -0
  104. blackboardxray-0.1.0/ui/src/components/EventTable.tsx +181 -0
  105. blackboardxray-0.1.0/ui/src/components/Facets.tsx +167 -0
  106. blackboardxray-0.1.0/ui/src/components/Form.tsx +164 -0
  107. blackboardxray-0.1.0/ui/src/components/Frame.tsx +335 -0
  108. blackboardxray-0.1.0/ui/src/components/Histogram.tsx +296 -0
  109. blackboardxray-0.1.0/ui/src/components/Inspector.tsx +338 -0
  110. blackboardxray-0.1.0/ui/src/components/RunGraph.tsx +485 -0
  111. blackboardxray-0.1.0/ui/src/components/RunTimeline.tsx +165 -0
  112. blackboardxray-0.1.0/ui/src/lib/admin.ts +230 -0
  113. blackboardxray-0.1.0/ui/src/lib/api.ts +315 -0
  114. blackboardxray-0.1.0/ui/src/lib/cn.ts +7 -0
  115. blackboardxray-0.1.0/ui/src/lib/events.ts +218 -0
  116. blackboardxray-0.1.0/ui/src/lib/format.ts +108 -0
  117. blackboardxray-0.1.0/ui/src/lib/http.ts +94 -0
  118. blackboardxray-0.1.0/ui/src/lib/query.ts +161 -0
  119. blackboardxray-0.1.0/ui/src/lib/session.tsx +222 -0
  120. blackboardxray-0.1.0/ui/src/lib/theme.ts +34 -0
  121. blackboardxray-0.1.0/ui/src/main.tsx +40 -0
  122. blackboardxray-0.1.0/ui/src/routes/Account.tsx +183 -0
  123. blackboardxray-0.1.0/ui/src/routes/Agents.tsx +175 -0
  124. blackboardxray-0.1.0/ui/src/routes/Board.tsx +148 -0
  125. blackboardxray-0.1.0/ui/src/routes/Boards.tsx +176 -0
  126. blackboardxray-0.1.0/ui/src/routes/Gate.tsx +369 -0
  127. blackboardxray-0.1.0/ui/src/routes/OrgSettings.tsx +404 -0
  128. blackboardxray-0.1.0/ui/src/routes/Platform.tsx +122 -0
  129. blackboardxray-0.1.0/ui/src/routes/ProjectSettings.tsx +293 -0
  130. blackboardxray-0.1.0/ui/src/styles/app.css +244 -0
  131. blackboardxray-0.1.0/ui/src/vite-env.d.ts +1 -0
  132. blackboardxray-0.1.0/ui/tokens/primitive/breakpoint.json +11 -0
  133. blackboardxray-0.1.0/ui/tokens/primitive/elevation.json +17 -0
  134. blackboardxray-0.1.0/ui/tokens/primitive/focus.json +8 -0
  135. blackboardxray-0.1.0/ui/tokens/primitive/material.json +72 -0
  136. blackboardxray-0.1.0/ui/tokens/primitive/motion.json +40 -0
  137. blackboardxray-0.1.0/ui/tokens/primitive/radius.json +11 -0
  138. blackboardxray-0.1.0/ui/tokens/primitive/space.json +16 -0
  139. blackboardxray-0.1.0/ui/tokens/primitive/type.json +84 -0
  140. blackboardxray-0.1.0/ui/tokens/primitive/zindex.json +12 -0
  141. blackboardxray-0.1.0/ui/tokens/semantic/color.dark.json +119 -0
  142. blackboardxray-0.1.0/ui/tokens/semantic/color.light.json +119 -0
  143. blackboardxray-0.1.0/ui/tokens/semantic/space.json +160 -0
  144. blackboardxray-0.1.0/ui/tokens/semantic/type.json +12 -0
  145. blackboardxray-0.1.0/ui/tsconfig.app.json +24 -0
  146. blackboardxray-0.1.0/ui/tsconfig.app.tsbuildinfo +1 -0
  147. blackboardxray-0.1.0/ui/tsconfig.json +7 -0
  148. blackboardxray-0.1.0/ui/tsconfig.node.json +16 -0
  149. blackboardxray-0.1.0/ui/tsconfig.node.tsbuildinfo +1 -0
  150. blackboardxray-0.1.0/ui/vite.config.ts +20 -0
  151. blackboardxray-0.1.0/uv.lock +1184 -0
@@ -0,0 +1,22 @@
1
+ # One answer per file type, so an editor does not have to guess and a diff does
2
+ # not carry somebody else's settings.
3
+ root = true
4
+
5
+ [*]
6
+ charset = utf-8
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
10
+ indent_style = space
11
+ indent_size = 4
12
+
13
+ [*.{js,jsx,ts,tsx,json,jsonc,css,html,yml,yaml,mjs}]
14
+ indent_size = 2
15
+
16
+ [*.md]
17
+ # A trailing double space is a line break in Markdown, and trimming it changes
18
+ # what the document says.
19
+ trim_trailing_whitespace = false
20
+
21
+ [Makefile]
22
+ indent_style = tab
@@ -0,0 +1,20 @@
1
+ # One line ending everywhere, decided here rather than by whichever machine
2
+ # checked the file out.
3
+ * text=auto eol=lf
4
+
5
+ # Generated. Marked so a diff view collapses them and a language survey does not
6
+ # count them as something somebody wrote.
7
+ CHANGELOG.md linguist-generated=true
8
+ uv.lock linguist-generated=true -diff
9
+ ui/package-lock.json linguist-generated=true -diff
10
+ ui/src/styles/tokens.css linguist-generated=true -diff
11
+ ui/src/styles/theme.css linguist-generated=true -diff
12
+ ui/tokens/primitive/color.json linguist-generated=true -diff
13
+
14
+ # Not the product. The interface's own source is, but its lockfile is not, and
15
+ # the fixtures are not.
16
+ examples/** linguist-documentation=true
17
+ docs/** linguist-documentation=true
18
+
19
+ *.png binary
20
+ *.woff2 binary
@@ -0,0 +1,80 @@
1
+ name: Bug report
2
+ description: Something behaves differently from what it says it does
3
+ labels: ["type:fix"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thank you for the report. The two fields that matter most are what you
9
+ expected and what happened, in that order.
10
+
11
+ If this is a security vulnerability, do not open an issue. `SECURITY.md`
12
+ says where to send it privately.
13
+
14
+ - type: textarea
15
+ id: expected
16
+ attributes:
17
+ label: What you expected
18
+ description: What should have happened, and what made you expect it.
19
+ validations:
20
+ required: true
21
+
22
+ - type: textarea
23
+ id: happened
24
+ attributes:
25
+ label: What happened instead
26
+ description: Include the exact error or the exact wrong value, not a paraphrase.
27
+ validations:
28
+ required: true
29
+
30
+ - type: textarea
31
+ id: reproduce
32
+ attributes:
33
+ label: How to see it
34
+ description: >
35
+ The shortest path from a fresh install to the wrong behaviour. A failing
36
+ test is the best possible version of this.
37
+ placeholder: |
38
+ 1. docker compose up -d
39
+ 2. Set up, then ...
40
+ 3. The runs list shows ...
41
+ validations:
42
+ required: true
43
+
44
+ - type: dropdown
45
+ id: where
46
+ attributes:
47
+ label: Where
48
+ options:
49
+ - The server or the API
50
+ - The interface
51
+ - The client that sends events
52
+ - Installing or upgrading
53
+ - Somewhere else, or not sure
54
+ validations:
55
+ required: true
56
+
57
+ - type: input
58
+ id: version
59
+ attributes:
60
+ label: Version
61
+ description: >
62
+ The image tag, or `pip show blackboardxray`, or the commit you are on.
63
+ placeholder: "0.1.0, or ghcr.io/moeinroghani/blackboardxray:1"
64
+ validations:
65
+ required: true
66
+
67
+ - type: input
68
+ id: postgres
69
+ attributes:
70
+ label: Postgres version
71
+ placeholder: "17.2"
72
+
73
+ - type: textarea
74
+ id: logs
75
+ attributes:
76
+ label: Logs
77
+ description: >
78
+ `docker compose logs xray`, or the server's output. Take the request
79
+ identifier out of the failing response if you have it.
80
+ render: text
@@ -0,0 +1,16 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Question, or an idea you want to talk through
4
+ url: https://github.com/MoeinRoghani/blackboardxray/discussions
5
+ about: >
6
+ Discussions, rather than the issue tracker. An issue is a thing to be
7
+ done; a question is not, and it gets a better answer there.
8
+ - name: Security vulnerability
9
+ url: https://github.com/MoeinRoghani/blackboardxray/security/advisories/new
10
+ about: >
11
+ Report it privately. Please do not open an issue for a vulnerability.
12
+ - name: How to run this yourself
13
+ url: https://github.com/MoeinRoghani/blackboardxray/blob/main/docs/self-hosting.md
14
+ about: >
15
+ Every environment variable, the proxy headers that matter, backup,
16
+ upgrading, and what this deliberately does not do.
@@ -0,0 +1,46 @@
1
+ name: Feature request
2
+ description: Something this should do and does not
3
+ labels: ["type:feat"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ The most useful requests describe a problem rather than a solution. What
9
+ you cannot currently find out about a run is more actionable than which
10
+ control you would like added.
11
+
12
+ - type: textarea
13
+ id: problem
14
+ attributes:
15
+ label: What you cannot do today
16
+ description: The situation you are in and what you end up doing instead.
17
+ validations:
18
+ required: true
19
+
20
+ - type: textarea
21
+ id: shape
22
+ attributes:
23
+ label: What would solve it
24
+ description: >
25
+ If you have a shape in mind, describe it. If you do not, say so; that is
26
+ a legitimate answer and often a better one.
27
+ validations:
28
+ required: true
29
+
30
+ - type: textarea
31
+ id: considered
32
+ attributes:
33
+ label: What you have already tried
34
+ description: >
35
+ Including whether an existing view nearly does it. Knowing what nearly
36
+ works says more than a blank field.
37
+
38
+ - type: checkboxes
39
+ id: scope
40
+ attributes:
41
+ label: Before you send this
42
+ options:
43
+ - label: >
44
+ I have read the end of `docs/self-hosting.md`, and this is not one of
45
+ the things the project has deliberately decided not to do.
46
+ required: true
@@ -0,0 +1,34 @@
1
+ ## What this changes
2
+
3
+ <!-- What is different afterwards, for somebody using this. One or two
4
+ sentences. The title is the release note; this is the reason. -->
5
+
6
+ ## Why
7
+
8
+ <!-- The problem. If there is an issue, say `Closes #N` and this can be short. -->
9
+
10
+ Closes #
11
+
12
+ ## How it was checked
13
+
14
+ <!-- What you ran, and what a reviewer should run to see it. If this fixes a
15
+ bug, name the test that failed before the change. -->
16
+
17
+ - [ ] `make verify` passes with `BLACKBOARDXRAY_TEST_DSN` set to a real database
18
+ - [ ] A test fails against the unmodified code and passes against this one
19
+ - [ ] The documentation this affects is updated in this pull request
20
+
21
+ ---
22
+
23
+ <!--
24
+ Two things this body must satisfy, both checked by a workflow.
25
+
26
+ NO PARENTHESES. The squash merge writes this body into the commit message and
27
+ release-please parses that message. A parenthesis defeats its parser silently:
28
+ the commit is dropped from the release notes while every check stays green.
29
+ Commas, semicolons and separate sentences carry the same meaning.
30
+
31
+ A BREAKING CHANGE carries both a `!` in the title and a `BREAKING CHANGE:`
32
+ footer here, stating what changed and what a caller writes instead. Both, not
33
+ either.
34
+ -->
@@ -0,0 +1,44 @@
1
+ # Four ecosystems, because this project has four. The interface has its own
2
+ # dependency tree and would otherwise never be updated, and the base images in
3
+ # the Dockerfile age the same way a package does.
4
+ version: 2
5
+ updates:
6
+ - package-ecosystem: "uv"
7
+ directory: "/"
8
+ schedule:
9
+ interval: "weekly"
10
+ commit-message:
11
+ prefix: "build"
12
+
13
+ - package-ecosystem: "npm"
14
+ directory: "/ui"
15
+ schedule:
16
+ interval: "weekly"
17
+ commit-message:
18
+ prefix: "build"
19
+ groups:
20
+ # One pull request for the interface's development tooling rather than
21
+ # one per package. They move together and are checked by the same build.
22
+ interface-tooling:
23
+ patterns:
24
+ - "@types/*"
25
+ - "typescript"
26
+ - "vite"
27
+ - "@vitejs/*"
28
+ - "tailwindcss"
29
+ - "@tailwindcss/*"
30
+ - "playwright"
31
+
32
+ - package-ecosystem: "github-actions"
33
+ directory: "/"
34
+ schedule:
35
+ interval: "weekly"
36
+ commit-message:
37
+ prefix: "ci"
38
+
39
+ - package-ecosystem: "docker"
40
+ directory: "/"
41
+ schedule:
42
+ interval: "weekly"
43
+ commit-message:
44
+ prefix: "build"
@@ -0,0 +1,136 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [main]
6
+ push:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ python:
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ python: ["3.11", "3.12", "3.13"]
19
+ services:
20
+ postgres:
21
+ image: postgres:17
22
+ env:
23
+ POSTGRES_USER: xray
24
+ POSTGRES_PASSWORD: xray
25
+ POSTGRES_DB: xray_test
26
+ ports: ["5432:5432"]
27
+ options: >-
28
+ --health-cmd pg_isready
29
+ --health-interval 5s
30
+ --health-timeout 3s
31
+ --health-retries 10
32
+ steps:
33
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
34
+ - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
35
+ with:
36
+ python-version: ${{ matrix.python }}
37
+ # From the lockfile, into a virtual environment. `--system` installs into
38
+ # the runner's own interpreter, which Debian marks externally managed and
39
+ # refuses, and `--frozen` is what makes this run the versions the lock
40
+ # names rather than whatever resolved this morning.
41
+ - run: uv sync --frozen --python ${{ matrix.python }}
42
+ - run: uv run ruff check src tests examples
43
+ - run: uv run ruff format --check src tests examples
44
+ - run: uv run mypy src
45
+ # The database tests skip where no server is named. Naming one here is
46
+ # what stops them silently skipping in the only place it matters.
47
+ - name: The suite, against a real Postgres
48
+ run: |
49
+ uv run pytest -q --strict-markers 2>&1 | tee /tmp/out
50
+ if grep -qE "[0-9]+ skipped" /tmp/out; then
51
+ echo
52
+ echo "A test skipped. Every skip in this suite means the database"
53
+ echo "was not reachable, which makes a green run a green run of"
54
+ echo "about a third of what there is."
55
+ exit 1
56
+ fi
57
+ env:
58
+ BLACKBOARDXRAY_TEST_DSN: postgresql://xray:xray@localhost:5432/xray_test
59
+
60
+ interface:
61
+ runs-on: ubuntu-latest
62
+ steps:
63
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
64
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
65
+ with:
66
+ node-version: "24"
67
+ - run: npm install
68
+ working-directory: ui
69
+ # The token pipeline, the raw-value gate, the contrast gate, and tsc.
70
+ # A screen that sets a value or a pair below its contrast floor fails here.
71
+ - run: npm run verify
72
+ working-directory: ui
73
+ - run: npm run build
74
+ working-directory: ui
75
+
76
+ # What `pip install blackboardxray[server]` actually gets. The interface
77
+ # is generated and gitignored, and a build backend that skips what
78
+ # version control ignores ships a server serving a blank page. It has
79
+ # happened twice: once because the wheel target did not name it, and
80
+ # once because the sdist did not and the wheel is built from the sdist.
81
+ - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
82
+ with:
83
+ python-version: "3.12"
84
+ - name: the interface is in both distributions
85
+ run: |
86
+ set -euo pipefail
87
+ uv build
88
+ # Listed to a file first. `grep -q` exits on its first match and
89
+ # closes the pipe, `tar` then fails writing to it, and `pipefail`
90
+ # fails the step for a check that actually passed.
91
+ tar tzf dist/*.tar.gz > /tmp/sdist.txt
92
+ unzip -l dist/*.whl > /tmp/wheel.txt
93
+ grep -q "server/web/index.html" /tmp/sdist.txt
94
+ echo "the sdist carries the interface"
95
+ grep -q "server/web/index.html" /tmp/wheel.txt
96
+ test "$(grep -c "server/web/assets/" /tmp/wheel.txt)" -gt 0
97
+ echo "the wheel carries the interface and its assets"
98
+
99
+ # The quickstart in the README, run as written. A compose file that has drifted
100
+ # from the Dockerfile is a first impression nobody recovers from, and it is
101
+ # exactly the kind of break the unit tests cannot see.
102
+ compose:
103
+ runs-on: ubuntu-latest
104
+ steps:
105
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
106
+ - run: docker compose up -d --build --wait --wait-timeout 300
107
+ - name: The platform answers and says it needs setting up
108
+ run: |
109
+ set -euo pipefail
110
+ test "$(curl -sf localhost:8900/api/v1/ready | jq -r .status)" = ready
111
+ test "$(curl -sf localhost:8900/api/v1/auth/state | jq -r .needs_setup)" = true
112
+ - name: The first run makes an owner, a project and a key that sends
113
+ run: |
114
+ set -euo pipefail
115
+ key=$(curl -sf -X POST localhost:8900/api/v1/setup \
116
+ -H 'content-type: application/json' \
117
+ -H 'origin: http://localhost:8900' \
118
+ -d '{"email":"ci@example.com","password":"a long enough phrase",
119
+ "organization":"CI","project":"Production"}' | jq -r .api_key)
120
+ test -n "$key"
121
+ curl -sf -X POST localhost:8900/api/v1/ingest \
122
+ -H "authorization: Bearer $key" -H 'content-type: application/json' \
123
+ -d '{"events":[{"board_id":"ci-1","kind":"run.opened"}]}' \
124
+ | jq -e '.stored == 1'
125
+ - name: Reading without a session is refused
126
+ run: |
127
+ set -euo pipefail
128
+ code=$(curl -s -o /dev/null -w '%{http_code}' \
129
+ localhost:8900/api/v1/projects/proj_nothing/overview)
130
+ test "$code" = 401
131
+ - name: The interface is served, not just the API
132
+ run: curl -sf localhost:8900/ | grep -q "<div id=\"root\">"
133
+ - if: failure()
134
+ run: docker compose logs --no-color | tail -80
135
+ - if: always()
136
+ run: docker compose down -v
@@ -0,0 +1,167 @@
1
+ # What a tag does.
2
+ #
3
+ # release-please keeps the version and the changelog from the commit subjects
4
+ # on main, and opens a pull request that holds them. Merging that pull request
5
+ # tags the release, and the tag builds the image and publishes the client.
6
+ #
7
+ # Nothing here is done by a person, because a release done by hand is a release
8
+ # whose steps are remembered differently each time. The one exception is the
9
+ # recovery path: a tag whose artefacts failed to publish can be published again
10
+ # without cutting a second tag, which is what `workflow_dispatch` is for.
11
+ name: Release
12
+
13
+ on:
14
+ push:
15
+ branches: [main]
16
+ workflow_dispatch:
17
+ inputs:
18
+ tag:
19
+ description: "An existing tag to publish, when its artefacts failed"
20
+ required: true
21
+ type: string
22
+
23
+ permissions:
24
+ contents: read
25
+
26
+ jobs:
27
+ # This job always runs and its one step does not. A skipped job propagates
28
+ # its skip down the whole needs chain, and overriding that in each job below
29
+ # was tried and did not work; having nothing skipped is simpler than
30
+ # explaining to every downstream job that a skip upstream was expected.
31
+ please:
32
+ runs-on: ubuntu-latest
33
+ permissions:
34
+ contents: write
35
+ pull-requests: write
36
+ outputs:
37
+ released: ${{ steps.please.outputs.release_created }}
38
+ tag: ${{ steps.please.outputs.tag_name }}
39
+ steps:
40
+ - uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445 # v4.1.3
41
+ id: please
42
+ if: github.event_name == 'push'
43
+ with:
44
+ config-file: release-please-config.json
45
+ manifest-file: .release-please-manifest.json
46
+
47
+ # What to publish, from whichever of the two ways this ran. One job, so the
48
+ # two below say `needs.plan` and never care which it was.
49
+ plan:
50
+ needs: [please]
51
+ if: needs.please.outputs.released == 'true' || github.event_name == 'workflow_dispatch'
52
+ runs-on: ubuntu-latest
53
+ outputs:
54
+ tag: ${{ steps.what.outputs.tag }}
55
+ version: ${{ steps.what.outputs.version }}
56
+ major: ${{ steps.what.outputs.major }}
57
+ minor: ${{ steps.what.outputs.minor }}
58
+ steps:
59
+ - id: what
60
+ env:
61
+ TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || needs.please.outputs.tag }}
62
+ run: |
63
+ set -euo pipefail
64
+ version="${TAG#v}"
65
+ echo "tag=$TAG" >> "$GITHUB_OUTPUT"
66
+ echo "version=$version" >> "$GITHUB_OUTPUT"
67
+ echo "major=${version%%.*}" >> "$GITHUB_OUTPUT"
68
+ rest="${version#*.}"
69
+ echo "minor=${rest%%.*}" >> "$GITHUB_OUTPUT"
70
+
71
+ image:
72
+ needs: [plan]
73
+ runs-on: ubuntu-latest
74
+ permissions:
75
+ contents: read
76
+ packages: write
77
+ # Signing the provenance attestation.
78
+ id-token: write
79
+ attestations: write
80
+ steps:
81
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
82
+ with:
83
+ ref: ${{ needs.plan.outputs.tag }}
84
+ - uses: docker/setup-qemu-action@49b3bc8e6bdd4a60e6116a5414239cba5943d3cf # v3.2.0
85
+ - uses: docker/setup-buildx-action@988b5a0280414f521da01fcc63a27aeeb4b104db # v3.6.1
86
+ - uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
87
+ with:
88
+ registry: ghcr.io
89
+ username: ${{ github.actor }}
90
+ password: ${{ secrets.GITHUB_TOKEN }}
91
+
92
+ # A registry name must be lowercase and a GitHub owner need not be, so
93
+ # `github.repository` cannot be used as an image name directly. The build
94
+ # refused the tag rather than mangling it, which is the right failure and
95
+ # is only useful once somebody folds the case here.
96
+ - name: the image name, lowercased
97
+ id: name
98
+ run: echo "image=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
99
+
100
+ - uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
101
+ id: build
102
+ with:
103
+ context: .
104
+ # A bill of materials and a provenance attestation, so somebody who
105
+ # pulled this can ask what is in it and who built it rather than
106
+ # taking the tag's word for it.
107
+ sbom: true
108
+ provenance: mode=max
109
+ # Two architectures, because half of the people running this are on
110
+ # an ARM laptop and the other half are on an x86 server.
111
+ platforms: linux/amd64,linux/arm64
112
+ push: true
113
+ # Four tags: the exact version, the minor line, the major line for
114
+ # somebody who wants fixes without reading release notes, and latest.
115
+ # Written without the leading `v`, because an image tag conventionally
116
+ # carries the version and not the git tag's name for it.
117
+ tags: |
118
+ ${{ steps.name.outputs.image }}:${{ needs.plan.outputs.version }}
119
+ ${{ steps.name.outputs.image }}:${{ needs.plan.outputs.major }}.${{ needs.plan.outputs.minor }}
120
+ ${{ steps.name.outputs.image }}:${{ needs.plan.outputs.major }}
121
+ ${{ steps.name.outputs.image }}:latest
122
+ cache-from: type=gha
123
+ cache-to: type=gha,mode=max
124
+
125
+ - name: attest what was built
126
+ uses: actions/attest-build-provenance@ef244123eb79f2f7a7e75d99086184180e6d0018 # v1.4.4
127
+ with:
128
+ subject-name: ${{ steps.name.outputs.image }}
129
+ subject-digest: ${{ steps.build.outputs.digest }}
130
+ push-to-registry: true
131
+
132
+ package:
133
+ needs: [plan]
134
+ runs-on: ubuntu-latest
135
+ environment: pypi
136
+ permissions:
137
+ # Trusted publishing: PyPI verifies this workflow rather than a token
138
+ # somebody pasted into a secret and then forgot they had.
139
+ id-token: write
140
+ steps:
141
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
142
+ with:
143
+ ref: ${{ needs.plan.outputs.tag }}
144
+ - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
145
+ with:
146
+ python-version: "3.12"
147
+ # The interface is built into the wheel, so `pip install blackboardxray`
148
+ # gives an application the client and gives an operator the whole server.
149
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
150
+ with:
151
+ node-version: "24"
152
+ - run: npm ci --prefix ui && npm run build --prefix ui
153
+ - run: uv build
154
+ - name: the interface is in the wheel
155
+ # It is generated and therefore gitignored, and a build backend that
156
+ # skips what version control ignores would ship a server serving a
157
+ # blank page. Checked here rather than discovered by whoever installs.
158
+ run: |
159
+ set -euo pipefail
160
+ wheel=$(ls dist/*.whl)
161
+ unzip -l "$wheel" > /tmp/wheel.txt
162
+ grep -q "server/web/index.html" /tmp/wheel.txt
163
+ echo "the interface is in $wheel"
164
+ # The twine this bundles has to understand the metadata hatchling writes.
165
+ # v1.12.2 shipped one that predates Metadata-Version 2.5 and reported the
166
+ # name as missing from a wheel that plainly has one.
167
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,92 @@
1
+ # What checks that a dependency has not gone bad, and that the code has not
2
+ # grown a known-shaped hole. Separate from CI because these are slower, and
3
+ # because a scheduled run catches an advisory published after a merge.
4
+ name: security
5
+
6
+ on:
7
+ push:
8
+ branches: [main]
9
+ pull_request:
10
+ branches: [main]
11
+ schedule:
12
+ # Weekly, so an advisory published against an unchanged dependency is found
13
+ # without waiting for somebody to open a pull request.
14
+ - cron: "0 6 * * 1"
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ codeql:
21
+ runs-on: ubuntu-latest
22
+ permissions:
23
+ security-events: write
24
+ contents: read
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ language: [python, javascript-typescript, actions]
29
+ steps:
30
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
31
+ - uses: github/codeql-action/init@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
32
+ with:
33
+ language: ${{ matrix.language }}
34
+ queries: security-extended
35
+ - uses: github/codeql-action/analyze@f09c1c0a94de965c15400f5634aa42fac8fb8f88 # v3.27.5
36
+ with:
37
+ category: "/language:${{ matrix.language }}"
38
+
39
+ dependency-review:
40
+ # Only meaningful on a pull request: it compares what a change adds against
41
+ # what was there before.
42
+ if: github.event_name == 'pull_request'
43
+ runs-on: ubuntu-latest
44
+ steps:
45
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
46
+ - uses: actions/dependency-review-action@3b139cfc5fae8b618d3eae3675e383bb1769c019 # v4.5.0
47
+ with:
48
+ fail-on-severity: high
49
+ # A copyleft licence arriving through a transitive dependency is a
50
+ # thing to decide about deliberately rather than discover later.
51
+ deny-licenses: AGPL-3.0, GPL-2.0, GPL-3.0
52
+
53
+ audit:
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
57
+ - uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
58
+ with:
59
+ python-version: "3.12"
60
+ - name: python dependencies
61
+ run: |
62
+ uv export --frozen --no-dev --no-emit-project \
63
+ --format requirements-txt > /tmp/requirements.txt
64
+ uvx pip-audit --requirement /tmp/requirements.txt --strict
65
+ - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
66
+ with:
67
+ node-version: "24"
68
+ - name: interface dependencies
69
+ working-directory: ui
70
+ # The interface ships as static files, so a vulnerability in a build
71
+ # tool is not a vulnerability in what is served. Production
72
+ # dependencies are the ones that reach a browser.
73
+ run: npm audit --omit=dev --audit-level=high
74
+
75
+ secrets:
76
+ runs-on: ubuntu-latest
77
+ steps:
78
+ - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
79
+ with:
80
+ fetch-depth: 0
81
+ # The binary rather than the action. The action scans the range between
82
+ # two commits, and on a push that has no base it reports a git error and
83
+ # exits non zero having found nothing, which is a red build that says
84
+ # the opposite of what it means. This scans the whole history, once.
85
+ - name: gitleaks
86
+ env:
87
+ VERSION: "8.21.2"
88
+ run: |
89
+ curl -sSfL \
90
+ "https://github.com/gitleaks/gitleaks/releases/download/v${VERSION}/gitleaks_${VERSION}_linux_x64.tar.gz" \
91
+ | tar -xz -C /tmp gitleaks
92
+ /tmp/gitleaks git . --redact --verbose --exit-code 1