tool-compass 2.5.0__tar.gz → 2.5.2__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 (52) hide show
  1. {tool_compass-2.5.0 → tool_compass-2.5.2}/.env.example +11 -1
  2. tool_compass-2.5.2/.gitattributes +17 -0
  3. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/dependabot.yml +1 -44
  4. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/workflows/ci.yml +140 -28
  5. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/workflows/publish.yml +50 -23
  6. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/workflows/release-binaries.yml +51 -44
  7. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/workflows/release.yml +128 -43
  8. {tool_compass-2.5.0 → tool_compass-2.5.2}/.gitignore +1 -0
  9. {tool_compass-2.5.0 → tool_compass-2.5.2}/CHANGELOG.md +66 -0
  10. {tool_compass-2.5.0 → tool_compass-2.5.2}/Dockerfile +16 -5
  11. {tool_compass-2.5.0 → tool_compass-2.5.2}/Makefile +12 -2
  12. {tool_compass-2.5.0 → tool_compass-2.5.2}/PKG-INFO +21 -8
  13. {tool_compass-2.5.0 → tool_compass-2.5.2}/README.md +18 -3
  14. {tool_compass-2.5.0 → tool_compass-2.5.2}/SCORECARD.md +2 -2
  15. {tool_compass-2.5.0 → tool_compass-2.5.2}/SHIP_GATE.md +1 -1
  16. {tool_compass-2.5.0 → tool_compass-2.5.2}/analytics.py +93 -58
  17. {tool_compass-2.5.0 → tool_compass-2.5.2}/backend_client_mcp.py +240 -25
  18. {tool_compass-2.5.0 → tool_compass-2.5.2}/backend_client_simple.py +1209 -148
  19. tool_compass-2.5.2/bootstrap.py +273 -0
  20. {tool_compass-2.5.0 → tool_compass-2.5.2}/chain_indexer.py +247 -31
  21. {tool_compass-2.5.0 → tool_compass-2.5.2}/cli.py +230 -64
  22. {tool_compass-2.5.0 → tool_compass-2.5.2}/config.py +93 -20
  23. {tool_compass-2.5.0 → tool_compass-2.5.2}/docker-compose.yml +32 -3
  24. tool_compass-2.5.2/docker-entrypoint.sh +59 -0
  25. {tool_compass-2.5.0 → tool_compass-2.5.2}/embedder.py +722 -76
  26. {tool_compass-2.5.0 → tool_compass-2.5.2}/gateway.py +1002 -122
  27. tool_compass-2.5.2/indexer.py +1918 -0
  28. {tool_compass-2.5.0 → tool_compass-2.5.2}/pyproject.toml +8 -10
  29. {tool_compass-2.5.0 → tool_compass-2.5.2}/scripts/check-org-urls.sh +5 -1
  30. {tool_compass-2.5.0 → tool_compass-2.5.2}/sync_manager.py +368 -98
  31. {tool_compass-2.5.0 → tool_compass-2.5.2}/ui.py +474 -12
  32. tool_compass-2.5.0/bootstrap.py +0 -128
  33. tool_compass-2.5.0/indexer.py +0 -1199
  34. {tool_compass-2.5.0 → tool_compass-2.5.2}/.dockerignore +0 -0
  35. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  36. {tool_compass-2.5.0 → tool_compass-2.5.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  37. {tool_compass-2.5.0 → tool_compass-2.5.2}/.pre-commit-config.yaml +0 -0
  38. {tool_compass-2.5.0 → tool_compass-2.5.2}/CODE_OF_CONDUCT.md +0 -0
  39. {tool_compass-2.5.0 → tool_compass-2.5.2}/CONTRIBUTING.md +0 -0
  40. {tool_compass-2.5.0 → tool_compass-2.5.2}/LICENSE +0 -0
  41. {tool_compass-2.5.0 → tool_compass-2.5.2}/SECURITY.md +0 -0
  42. {tool_compass-2.5.0 → tool_compass-2.5.2}/_version.py +0 -0
  43. {tool_compass-2.5.0 → tool_compass-2.5.2}/compass_config.example.json +0 -0
  44. {tool_compass-2.5.0 → tool_compass-2.5.2}/docs/index.md +0 -0
  45. {tool_compass-2.5.0 → tool_compass-2.5.2}/fly.toml +0 -0
  46. {tool_compass-2.5.0 → tool_compass-2.5.2}/llms.txt +0 -0
  47. {tool_compass-2.5.0 → tool_compass-2.5.2}/requirements-dev.txt +0 -0
  48. {tool_compass-2.5.0 → tool_compass-2.5.2}/requirements.txt +0 -0
  49. {tool_compass-2.5.0 → tool_compass-2.5.2}/scripts/regenerate-scorecard.sh +0 -0
  50. {tool_compass-2.5.0 → tool_compass-2.5.2}/scripts/sync-version.mjs +0 -0
  51. {tool_compass-2.5.0 → tool_compass-2.5.2}/scripts/verify-metrics.sh +0 -0
  52. {tool_compass-2.5.0 → tool_compass-2.5.2}/tool_manifest.py +0 -0
@@ -18,9 +18,19 @@
18
18
  # External Services
19
19
  # =============================================================================
20
20
 
21
- # Ollama server URL for embeddings
21
+ # Ollama server URL for embeddings (used when embedding_provider=ollama)
22
22
  OLLAMA_URL=http://localhost:11434
23
23
 
24
+ # Embedding provider override (ollama | openai | openai-compatible).
25
+ # Wins over compass_config.json embedding_provider for first-run bootstrap.
26
+ # TOOL_COMPASS_EMBEDDING_PROVIDER=ollama
27
+
28
+ # API key for openai / openai-compatible embeddings. Wins over the
29
+ # embedding_api_key field in compass_config.json — keep the secret out of
30
+ # the on-disk config. Required when embedding_provider is openai or
31
+ # openai-compatible (unless the server accepts unauthenticated calls).
32
+ # TOOL_COMPASS_EMBEDDING_API_KEY=
33
+
24
34
  # ComfyUI server URL (for AI image generation backend)
25
35
  COMFYUI_URL=http://localhost:8188
26
36
 
@@ -0,0 +1,17 @@
1
+ # Shell scripts and the Dockerfile MUST keep LF, on every platform.
2
+ #
3
+ # Earned on the sibling xrpl-camp repo the same day this landed: an entrypoint
4
+ # script checked out with CRLF on Windows (core.autocrlf) makes the shebang
5
+ # `#!/bin/sh\r`, and Linux then tries to exec an interpreter literally named
6
+ # `/bin/sh\r`. It fails with "no such file or directory" naming the SCRIPT, so
7
+ # it reads like a missing file rather than a line-ending problem.
8
+ #
9
+ # CI never catches it -- a Linux runner checks out LF and builds fine. Only a
10
+ # contributor building the image on Windows hits it, which is the worst place
11
+ # for a defect to live: invisible to the people who could fix it.
12
+ *.sh text eol=lf
13
+ Dockerfile text eol=lf
14
+ docker-entrypoint.sh text eol=lf
15
+
16
+ # Keep the rest normalised in the repo and native in the working tree.
17
+ * text=auto
@@ -16,25 +16,6 @@ updates:
16
16
  patterns: ["*"]
17
17
  update-types: ["minor", "patch"]
18
18
 
19
- # Daily security-only overlay (CT-B-006) — CVE updates from the GitHub
20
- # Advisory Database don't wait the monthly cadence. open-pull-requests-limit
21
- # raised to 10 since these are infrequent and load-bearing.
22
- # NOTE: Dependabot security advisories are auto-emitted; this second entry
23
- # exists to give them their own schedule/labels/PR cap independent of the
24
- # monthly version-update entry above. The `allow: dependency-type: all`
25
- # block scopes this entry to all dependencies; security advisories are
26
- # always opened regardless of the version-update grouping rules.
27
- - package-ecosystem: "pip"
28
- directory: "/"
29
- schedule:
30
- interval: "daily"
31
- open-pull-requests-limit: 10
32
- allow:
33
- - dependency-type: "all"
34
- labels:
35
- - "security"
36
- - "dependencies"
37
-
38
19
  # =============================================================================
39
20
  # github-actions — workflow file action references
40
21
  # =============================================================================
@@ -48,22 +29,10 @@ updates:
48
29
  patterns: ["*"]
49
30
  update-types: ["minor", "patch"]
50
31
 
51
- # Daily security-only overlay for actions (CT-B-006).
52
- - package-ecosystem: "github-actions"
53
- directory: "/"
54
- schedule:
55
- interval: "daily"
56
- open-pull-requests-limit: 10
57
- allow:
58
- - dependency-type: "all"
59
- labels:
60
- - "security"
61
- - "dependencies"
62
-
63
32
  # =============================================================================
64
33
  # docker — Dockerfile base-image FROM directives (CT-B-005)
65
34
  # =============================================================================
66
- # Pairs with the digest-pinned `FROM python:3.11-slim@sha256:...` in
35
+ # Pairs with the digest-pinned `FROM python:3.12-slim@sha256:...` in
67
36
  # Dockerfile (CT-B-003). Without this ecosystem entry the digest pin
68
37
  # would become a stale anchor with no machine-readable refresh path.
69
38
  - package-ecosystem: "docker"
@@ -75,15 +44,3 @@ updates:
75
44
  all-docker:
76
45
  patterns: ["*"]
77
46
  update-types: ["minor", "patch"]
78
-
79
- # Daily security-only overlay for docker base images (CT-B-006).
80
- - package-ecosystem: "docker"
81
- directory: "/"
82
- schedule:
83
- interval: "daily"
84
- open-pull-requests-limit: 10
85
- allow:
86
- - dependency-type: "all"
87
- labels:
88
- - "security"
89
- - "dependencies"
@@ -1,3 +1,14 @@
1
+ # --- Measured CI cost (dogfood-swarm SKILL item 8) --------------------------
2
+ # 10 billable min per push across 8 jobs (6.1 min actual wall time),
3
+ # measured 2026-07-04 from run 28722872805. Most expensive jobs:
4
+ # 2 min Org URL sanity check (1.1 min wall)
5
+ # 2 min Python 3.12 on ubuntu-latest (1.9 min wall)
6
+ # 2 min Integration Tests (1.4 min wall)
7
+ # GitHub bills per JOB, rounded UP to the minute, x2 Windows / x10 macOS, so
8
+ # billable minutes exceed wall time - many short jobs cost more than one long
9
+ # one. This number is measured, never estimated; re-measure after any change:
10
+ # gh api repos/mcp-tool-shop-org/tool-compass/actions/runs/<id>/jobs
11
+ # ---------------------------------------------------------------------------
1
12
  # Tool Compass CI Pipeline
2
13
  # Runs tests on pushes and PRs that touch code or config.
3
14
  # Also handles GitHub Pages deploy for site/** changes on main (folded in from
@@ -15,6 +26,12 @@ on:
15
26
  - 'requirements*.txt'
16
27
  - 'pyproject.toml'
17
28
  - 'Dockerfile'
29
+ - 'docker-compose.yml'
30
+ - 'Makefile'
31
+ - 'fly.toml'
32
+ - '.pre-commit-config.yaml'
33
+ - '.dockerignore'
34
+ - 'npm/**'
18
35
  - 'site/**'
19
36
  - '.github/workflows/**'
20
37
  pull_request:
@@ -26,6 +43,12 @@ on:
26
43
  - 'requirements*.txt'
27
44
  - 'pyproject.toml'
28
45
  - 'Dockerfile'
46
+ - 'docker-compose.yml'
47
+ - 'Makefile'
48
+ - 'fly.toml'
49
+ - '.pre-commit-config.yaml'
50
+ - '.dockerignore'
51
+ - 'npm/**'
29
52
  - 'site/**'
30
53
  - '.github/workflows/**'
31
54
  # TST-FT-002: nightly-fuzz job runs Hypothesis in `nightly` profile
@@ -73,7 +96,7 @@ jobs:
73
96
  - name: Set up Python (pre-commit)
74
97
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
75
98
  with:
76
- python-version: '3.11'
99
+ python-version: '3.12'
77
100
 
78
101
  # CT-B-018: run the pre-commit hooks (ruff format + ruff check +
79
102
  # standard hygiene + gitleaks). Same hooks the contributor runs
@@ -122,22 +145,46 @@ jobs:
122
145
  timeout-minutes: 15
123
146
  # PR-scoped concurrency (see workflow-top NOTE).
124
147
  concurrency:
125
- group: test-${{ matrix.python-version }}-${{ github.event.pull_request.number || github.run_id }}
148
+ group: test-${{ matrix.python-version }}-${{ matrix.os }}-${{ github.event.pull_request.number || github.run_id }}
126
149
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
127
150
  permissions:
128
151
  contents: read
152
+ # Windows runners default to pwsh, which does not accept `\` line
153
+ # continuations used below. Git Bash is on the image.
154
+ defaults:
155
+ run:
156
+ shell: bash
129
157
 
130
158
  strategy:
131
159
  fail-fast: false
132
160
  matrix:
133
- os: [ubuntu-latest]
134
- # Two versions per user's hard rule (max 2 Python versions). Covers
135
- # current stable + latest supported, keeping requires-python honest.
136
- # 3.13 dropped from CI matrix: hnswlib 0.8.0's cp313 wheel SIGILLs on
137
- # GitHub ubuntu-latest runners (AVX instruction gap). pyproject still
138
- # declares 3.13 support; once hnswlib ships a forgiving cp313 wheel
139
- # or we switch to a pluggable vector backend (IDX-FT-001), re-add it.
140
- python-version: ['3.11', '3.12']
161
+ # Two-cell OS matrix (ubuntu + windows). macos-latest is x10 billable
162
+ # and is covered by the release-binaries darwin-arm64 freeze, not
163
+ # every-push pytest. Do not revive 3.11/3.13: those were dropped for
164
+ # runner SIGILL on hnswlib/numpy wheels (see comment below).
165
+ os: [ubuntu-latest, windows-latest]
166
+ # One version, because one is all this repo can currently test.
167
+ #
168
+ # 3.13 was dropped earlier: hnswlib 0.8.0's cp313 wheel SIGILLs on
169
+ # GitHub ubuntu-latest runners (AVX instruction gap).
170
+ #
171
+ # 3.11 is dropped here 2026-09-08 for the SAME reason, now reaching
172
+ # numpy: pytest died at collection with "Illegal instruction (core
173
+ # dumped)" importing numpy._core._multiarray_umath. Reproducible across
174
+ # a rerun, and 3.12 passed the same commit both times. Version pinning
175
+ # would not have helped -- the last green run used the SAME numpy 2.1.3
176
+ # on the SAME runner image, so the wheel's CPU dispatch, not its
177
+ # version, is what changed underneath.
178
+ #
179
+ # READ THIS AS A RUNNER LIMITATION, NOT A LANGUAGE ONE. Nothing here
180
+ # needs 3.12 syntax; 3.11 is dropped because prebuilt wheels crash on
181
+ # GitHub's ubuntu-latest CPUs, not because the code stopped supporting
182
+ # it. requires-python was raised to >=3.12 in the same commit so the
183
+ # package stops claiming an interpreter no gate exercises -- a green
184
+ # badge over an untested runtime is worse than a narrow matrix.
185
+ # Reinstating 3.11 is a deliberate decision with its own test strategy
186
+ # (a self-hosted runner, or a source build of hnswlib/numpy).
187
+ python-version: ['3.12']
141
188
 
142
189
  steps:
143
190
  - name: Checkout repository
@@ -159,39 +206,39 @@ jobs:
159
206
  - name: Run tests
160
207
  run: |
161
208
  pytest -v --tb=short -m "not integration" \
162
- --junitxml=junit-${{ matrix.python-version }}.xml
209
+ --junitxml=junit-${{ matrix.python-version }}-${{ matrix.os }}.xml
163
210
 
164
211
  - name: Upload JUnit report
165
212
  if: always()
166
213
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
167
214
  with:
168
- name: junit-reports-${{ matrix.python-version }}
169
- path: junit-${{ matrix.python-version }}.xml
215
+ name: junit-reports-${{ matrix.python-version }}-${{ matrix.os }}
216
+ path: junit-${{ matrix.python-version }}-${{ matrix.os }}.xml
170
217
  if-no-files-found: warn
171
218
  # CT-B-012: JUnit reports are short-lived diagnostic data — only
172
219
  # useful for the most recent few runs. Default 90d is wasted.
173
220
  retention-days: 14
174
221
 
175
222
  - name: Run tests with coverage
176
- if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
223
+ if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
177
224
  run: |
178
225
  pytest --cov=. --cov-report=xml --cov-report=term -m "not integration"
179
226
 
180
227
  - name: Upload coverage to Codecov
181
- if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
228
+ if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
182
229
  uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v5
183
230
  with:
184
231
  file: coverage.xml
185
232
  fail_ci_if_error: false
186
233
 
187
234
  - name: Run pip-audit (report only)
188
- if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
235
+ if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
189
236
  run: |
190
237
  pip-audit -r requirements.txt --format json --output pip-audit-report.json || true
191
238
  # Non-blocking: full output captured as artifact for reviewers.
192
239
 
193
240
  - name: Upload pip-audit report
194
- if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
241
+ if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
195
242
  uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
196
243
  with:
197
244
  name: pip-audit-report
@@ -205,10 +252,34 @@ jobs:
205
252
  # `pip-audit -r requirements.txt` reports no known vulnerabilities. The
206
253
  # report-only artifact step above still runs for the full JSON snapshot.
207
254
  - name: Audit dependencies for vulnerabilities
208
- if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
255
+ if: matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'
209
256
  run: |
210
257
  pip-audit -r requirements.txt
211
258
 
259
+ npm-wrapper:
260
+ name: npm wrapper tests
261
+ runs-on: ubuntu-latest
262
+ timeout-minutes: 5
263
+ concurrency:
264
+ group: npm-wrapper-${{ github.event.pull_request.number || github.run_id }}
265
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
266
+ permissions:
267
+ contents: read
268
+ steps:
269
+ - name: Checkout repository
270
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
271
+
272
+ - name: Setup Node
273
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6
274
+ with:
275
+ node-version: 22
276
+
277
+ - name: Install and test npm wrapper
278
+ working-directory: npm
279
+ run: |
280
+ npm install --no-audit --no-fund
281
+ npm test
282
+
212
283
  # TST-FT-002: nightly Hypothesis fuzzing. Runs on schedule (Mon/Wed/Fri 09:00
213
284
  # UTC) or manually via workflow_dispatch. Never runs on push/PR — nightly-only.
214
285
  # On failure, opens a tracking issue via actions/github-script.
@@ -229,7 +300,7 @@ jobs:
229
300
  - name: Set up Python
230
301
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
231
302
  with:
232
- python-version: '3.11'
303
+ python-version: '3.12'
233
304
  cache: 'pip'
234
305
  cache-dependency-path: 'requirements*.txt'
235
306
 
@@ -292,7 +363,7 @@ jobs:
292
363
  - name: Set up Python
293
364
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
294
365
  with:
295
- python-version: '3.11'
366
+ python-version: '3.12'
296
367
  cache: 'pip'
297
368
  cache-dependency-path: 'requirements*.txt'
298
369
 
@@ -338,14 +409,26 @@ jobs:
338
409
  continue-on-error: true # Integration tests may be flaky
339
410
 
340
411
  docker:
341
- name: Docker Build
412
+ name: Docker ${{ matrix.target }}
342
413
  runs-on: ubuntu-latest
343
414
  needs: test
344
415
  # CT-B-011: docker buildx + gha cache; 20 min covers a cold-cache build.
345
416
  timeout-minutes: 20
417
+ strategy:
418
+ fail-fast: false
419
+ matrix:
420
+ include:
421
+ - target: mcp-gateway
422
+ tag: tool-compass:test-gateway
423
+ port: 8080
424
+ smoke: health
425
+ - target: production
426
+ tag: tool-compass:test-ui
427
+ port: 7860
428
+ smoke: bind
346
429
  # PR-scoped concurrency (see workflow-top NOTE).
347
430
  concurrency:
348
- group: docker-${{ github.event.pull_request.number || github.run_id }}
431
+ group: docker-${{ matrix.target }}-${{ github.event.pull_request.number || github.run_id }}
349
432
  cancel-in-progress: ${{ github.event_name == 'pull_request' }}
350
433
  permissions:
351
434
  contents: read
@@ -355,16 +438,45 @@ jobs:
355
438
  uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
356
439
 
357
440
  - name: Set up Docker Buildx
358
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
441
+ uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
359
442
 
360
443
  - name: Build Docker image
361
- uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
444
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
362
445
  with:
363
446
  context: .
447
+ target: ${{ matrix.target }}
364
448
  push: false
365
- tags: tool-compass:test
366
- cache-from: type=gha
367
- cache-to: type=gha,mode=max
449
+ load: true
450
+ tags: ${{ matrix.tag }}
451
+ cache-from: type=gha,scope=ci-${{ matrix.target }}
452
+ cache-to: type=gha,mode=max,scope=ci-${{ matrix.target }}
453
+
454
+ - name: Smoke run
455
+ run: |
456
+ set -euo pipefail
457
+ docker run -d --name tc-smoke -p ${{ matrix.port }}:${{ matrix.port }} ${{ matrix.tag }}
458
+ cleanup() { docker logs tc-smoke || true; docker rm -f tc-smoke || true; }
459
+ trap cleanup EXIT
460
+ for _ in $(seq 1 45); do
461
+ if [ "${{ matrix.smoke }}" = "health" ]; then
462
+ if curl -sf "http://127.0.0.1:${{ matrix.port }}/health"; then
463
+ echo "gateway /health ok"
464
+ exit 0
465
+ fi
466
+ else
467
+ # Bind smoke: any HTTP response (or even a reset after accept)
468
+ # means the UI process bound 7860. Do not use curl -f — Gradio
469
+ # may 4xx before the app is fully ready.
470
+ if curl -sS --connect-timeout 2 -o /dev/null \
471
+ "http://127.0.0.1:${{ matrix.port }}/"; then
472
+ echo "ui port ${{ matrix.port }} bound"
473
+ exit 0
474
+ fi
475
+ fi
476
+ sleep 2
477
+ done
478
+ echo "::error::docker smoke (${{ matrix.target }}) did not become ready on port ${{ matrix.port }}"
479
+ exit 1
368
480
 
369
481
  # ===========================================================================
370
482
  # GitHub Pages build + deploy (folded in from former pages.yml)
@@ -425,4 +537,4 @@ jobs:
425
537
  url: ${{ steps.deployment.outputs.page_url }}
426
538
  steps:
427
539
  - id: deployment
428
- uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
540
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -60,7 +60,7 @@ jobs:
60
60
  - name: Set up Python
61
61
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
62
62
  with:
63
- python-version: '3.11'
63
+ python-version: '3.12'
64
64
 
65
65
  - name: Install build tools
66
66
  run: |
@@ -107,7 +107,7 @@ jobs:
107
107
  path: dist/
108
108
 
109
109
  - name: Publish to PyPI
110
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
110
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
111
111
  with:
112
112
  # CT-B-009: emit SLSA build provenance attestations for the wheel
113
113
  # and sdist. PyPI verifies the attestation against the workflow's
@@ -116,7 +116,7 @@ jobs:
116
116
  attestations: true
117
117
 
118
118
  docker:
119
- name: Publish to GHCR
119
+ name: Publish ${{ matrix.stage }} to GHCR
120
120
  runs-on: ubuntu-latest
121
121
  # PyPI ships first (CDS-B-005). If PyPI fails, Docker must not ship a
122
122
  # half-release; if PyPI succeeds, Docker becomes the recoverable tail.
@@ -124,6 +124,16 @@ jobs:
124
124
  # CT-B-011: multi-arch buildx (linux/amd64 + linux/arm64) + push to GHCR
125
125
  # is the longest leg; 30 min covers the warm cache path with headroom.
126
126
  timeout-minutes: 30
127
+ strategy:
128
+ fail-fast: false
129
+ matrix:
130
+ include:
131
+ # mcp-gateway is the last Dockerfile stage; pin it explicitly so a
132
+ # later debug/sbom stage cannot silently retarget :latest.
133
+ - stage: gateway
134
+ target: mcp-gateway
135
+ - stage: ui
136
+ target: production
127
137
  permissions:
128
138
  contents: read
129
139
  packages: write
@@ -142,7 +152,7 @@ jobs:
142
152
  ref: ${{ github.event.workflow_run.head_branch || github.event.release.tag_name || github.ref_name }}
143
153
 
144
154
  - name: Log in to GHCR
145
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
155
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
146
156
  with:
147
157
  registry: ${{ env.REGISTRY }}
148
158
  username: ${{ github.actor }}
@@ -164,43 +174,52 @@ jobs:
164
174
  V="${REF#v}"
165
175
  echo "version=${V}" >> "$GITHUB_OUTPUT"
166
176
  echo "major_minor=$(echo "$V" | cut -d. -f1-2)" >> "$GITHUB_OUTPUT"
167
- echo "resolved=${V} (event=${{ github.event_name }} ref=${REF})"
177
+ echo "resolved=${V} (event=${{ github.event_name }} ref=${REF} stage=${{ matrix.stage }})"
168
178
 
169
179
  - name: Extract metadata
170
180
  id: meta
171
- uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
181
+ uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0
172
182
  with:
173
183
  images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
184
+ flavor: |
185
+ latest=false
174
186
  # type=raw from the explicitly-resolved version (see Resolve step) —
175
187
  # NOT type=semver, which silently emits nothing under workflow_run
176
188
  # because github.ref is main, not the tag. Each enable=... gates its
177
189
  # raw tag so an empty/missing version never pushes a bogus tag.
190
+ # Gateway: :latest :gateway :X.Y.Z :X.Y
191
+ # UI: :ui :ui-X.Y.Z
178
192
  tags: |
179
- type=raw,value=${{ steps.ver.outputs.version }},enable=${{ steps.ver.outputs.version != '' }}
180
- type=raw,value=${{ steps.ver.outputs.major_minor }},enable=${{ steps.ver.outputs.major_minor != '' }}
181
- type=sha
182
- type=raw,value=latest,enable={{is_default_branch}}
193
+ type=raw,value=${{ steps.ver.outputs.version }},enable=${{ matrix.stage == 'gateway' && steps.ver.outputs.version != '' }}
194
+ type=raw,value=${{ steps.ver.outputs.major_minor }},enable=${{ matrix.stage == 'gateway' && steps.ver.outputs.major_minor != '' }}
195
+ type=raw,value=latest,enable=${{ matrix.stage == 'gateway' }}
196
+ type=raw,value=gateway,enable=${{ matrix.stage == 'gateway' }}
197
+ type=sha,enable=${{ matrix.stage == 'gateway' }}
198
+ type=raw,value=ui-${{ steps.ver.outputs.version }},enable=${{ matrix.stage == 'ui' && steps.ver.outputs.version != '' }}
199
+ type=raw,value=ui,enable=${{ matrix.stage == 'ui' }}
200
+ type=sha,prefix=ui-,enable=${{ matrix.stage == 'ui' }}
183
201
 
184
202
  # QEMU is required to cross-build linux/arm64 from an amd64 runner.
185
203
  - name: Set up QEMU
186
204
  # SHA-pinned per SLSA L3 supply-chain hygiene (CT-B-002). publish job
187
205
  # carries packages:write — a compromised mutable v3 tag would inherit
188
206
  # GHCR write capability during a release window. Bump via dependabot.
189
- uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
207
+ uses: docker/setup-qemu-action@1f40c72289eff860ee54a304f1438e3cff362e0a # v4.3.0
190
208
 
191
209
  - name: Set up Docker Buildx
192
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
210
+ uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
193
211
 
194
212
  - name: Build and push
195
- uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
213
+ uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
196
214
  with:
197
215
  context: .
216
+ target: ${{ matrix.target }}
198
217
  push: true
199
218
  platforms: linux/amd64,linux/arm64
200
219
  tags: ${{ steps.meta.outputs.tags }}
201
220
  labels: ${{ steps.meta.outputs.labels }}
202
- cache-from: type=gha
203
- cache-to: type=gha,mode=max
221
+ cache-from: type=gha,scope=ghcr-${{ matrix.target }}
222
+ cache-to: type=gha,mode=max,scope=ghcr-${{ matrix.target }}
204
223
  # CT-B-009: SLSA provenance mode=max emits buildx provenance
205
224
  # attestation (including all input source digests) attached to the
206
225
  # image manifest. sbom: true emits an SPDX SBOM attestation in the
@@ -234,7 +253,7 @@ jobs:
234
253
  - name: Set up Python
235
254
  uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
236
255
  with:
237
- python-version: '3.11'
256
+ python-version: '3.12'
238
257
 
239
258
  - name: PyPI smoke — install + --version matches tag
240
259
  run: |
@@ -264,22 +283,30 @@ jobs:
264
283
  }
265
284
 
266
285
  - name: Log in to GHCR (pull)
267
- uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
286
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
268
287
  with:
269
288
  registry: ${{ env.REGISTRY }}
270
289
  username: ${{ github.actor }}
271
290
  password: ${{ secrets.GITHUB_TOKEN }}
272
291
 
273
- - name: Docker smoke — pull + --version matches tag
292
+ - name: Docker smoke — pull gateway + UI, --version matches tag
274
293
  run: |
275
294
  set -euo pipefail
276
295
  TAG="${{ steps.ver.outputs.tag }}"
277
296
  VERSION="${{ steps.ver.outputs.version }}"
278
- IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}"
279
- docker pull "$IMAGE"
280
- OUT="$(docker run --rm --entrypoint tool-compass "$IMAGE" --version 2>&1 || true)"
281
- echo "docker --version → $OUT"
297
+ GATEWAY="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${VERSION}"
298
+ UI="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:ui-${VERSION}"
299
+ docker pull "$GATEWAY"
300
+ docker pull "$UI"
301
+ OUT="$(docker run --rm --entrypoint tool-compass "$GATEWAY" --version 2>&1 || true)"
302
+ echo "docker gateway --version → $OUT"
282
303
  echo "$OUT" | grep -Fq "${VERSION}" || {
283
- echo "::error::Docker smoke failed: --version output did not contain ${VERSION}"
304
+ echo "::error::Docker gateway smoke failed: --version output did not contain ${VERSION}"
305
+ exit 1
306
+ }
307
+ OUT_UI="$(docker run --rm --entrypoint tool-compass "$UI" --version 2>&1 || true)"
308
+ echo "docker ui --version → $OUT_UI"
309
+ echo "$OUT_UI" | grep -Fq "${VERSION}" || {
310
+ echo "::error::Docker UI smoke failed: --version output did not contain ${VERSION}"
284
311
  exit 1
285
312
  }