tool-compass 2.2.1__tar.gz → 2.3.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 (91) hide show
  1. tool_compass-2.3.0/.dockerignore +84 -0
  2. tool_compass-2.3.0/.github/dependabot.yml +89 -0
  3. {tool_compass-2.2.1 → tool_compass-2.3.0}/.github/workflows/ci.yml +93 -6
  4. {tool_compass-2.2.1 → tool_compass-2.3.0}/.github/workflows/publish.yml +53 -4
  5. tool_compass-2.3.0/.github/workflows/release-binaries.yml +268 -0
  6. {tool_compass-2.2.1 → tool_compass-2.3.0}/.gitignore +14 -0
  7. tool_compass-2.3.0/.pre-commit-config.yaml +55 -0
  8. tool_compass-2.3.0/CHANGELOG.md +440 -0
  9. {tool_compass-2.2.1 → tool_compass-2.3.0}/CONTRIBUTING.md +45 -26
  10. {tool_compass-2.2.1 → tool_compass-2.3.0}/Dockerfile +26 -9
  11. {tool_compass-2.2.1 → tool_compass-2.3.0}/Makefile +15 -8
  12. {tool_compass-2.2.1 → tool_compass-2.3.0}/PKG-INFO +40 -13
  13. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.es.md +53 -31
  14. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.fr.md +57 -31
  15. tool_compass-2.3.0/README.hi.md +342 -0
  16. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.it.md +72 -46
  17. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.ja.md +65 -40
  18. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.md +37 -11
  19. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.pt-BR.md +53 -29
  20. {tool_compass-2.2.1 → tool_compass-2.3.0}/README.zh.md +73 -51
  21. tool_compass-2.3.0/SCORECARD.md +36 -0
  22. {tool_compass-2.2.1 → tool_compass-2.3.0}/SECURITY.md +22 -7
  23. {tool_compass-2.2.1 → tool_compass-2.3.0}/analytics.py +22 -0
  24. {tool_compass-2.2.1 → tool_compass-2.3.0}/backend_client_mcp.py +47 -6
  25. tool_compass-2.3.0/backend_client_simple.py +1804 -0
  26. {tool_compass-2.2.1 → tool_compass-2.3.0}/chain_indexer.py +75 -9
  27. tool_compass-2.3.0/cli.py +1598 -0
  28. {tool_compass-2.2.1 → tool_compass-2.3.0}/config.py +174 -11
  29. tool_compass-2.3.0/embedder.py +591 -0
  30. {tool_compass-2.2.1 → tool_compass-2.3.0}/gateway.py +896 -213
  31. {tool_compass-2.2.1 → tool_compass-2.3.0}/indexer.py +312 -147
  32. {tool_compass-2.2.1 → tool_compass-2.3.0}/llms.txt +20 -4
  33. {tool_compass-2.2.1 → tool_compass-2.3.0/npm}/CHANGELOG.md +93 -11
  34. tool_compass-2.3.0/npm/LICENSE +21 -0
  35. tool_compass-2.3.0/npm/README.md +171 -0
  36. tool_compass-2.3.0/npm/bin/tool-compass.js +14 -0
  37. tool_compass-2.3.0/npm/package.json +45 -0
  38. tool_compass-2.3.0/npm/test/smoke.test.js +54 -0
  39. {tool_compass-2.2.1 → tool_compass-2.3.0}/pyproject.toml +24 -6
  40. tool_compass-2.3.0/scripts/regenerate-scorecard.sh +110 -0
  41. tool_compass-2.3.0/scripts/verify-metrics.sh +125 -0
  42. tool_compass-2.3.0/site/astro.config.mjs +60 -0
  43. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content/docs/handbook/architecture.md +6 -3
  44. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content/docs/handbook/beginners.md +15 -6
  45. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content/docs/handbook/configuration.md +3 -3
  46. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content/docs/handbook/getting-started.md +26 -6
  47. tool_compass-2.3.0/site/src/content/docs/handbook/index.md +48 -0
  48. {tool_compass-2.2.1 → tool_compass-2.3.0}/sync_manager.py +29 -11
  49. {tool_compass-2.2.1 → tool_compass-2.3.0}/tool_manifest.py +16 -2
  50. tool_compass-2.3.0/ui.py +1851 -0
  51. tool_compass-2.2.1/.dockerignore +0 -56
  52. tool_compass-2.2.1/.github/dependabot.yml +0 -24
  53. tool_compass-2.2.1/AUDIT_REPORT.md +0 -245
  54. tool_compass-2.2.1/README.hi.md +0 -323
  55. tool_compass-2.2.1/SCORECARD.md +0 -36
  56. tool_compass-2.2.1/backend_client_simple.py +0 -861
  57. tool_compass-2.2.1/cli.py +0 -283
  58. tool_compass-2.2.1/embedder.py +0 -432
  59. tool_compass-2.2.1/site/astro.config.mjs +0 -30
  60. tool_compass-2.2.1/site/src/content/docs/handbook/index.md +0 -30
  61. tool_compass-2.2.1/ui.py +0 -1337
  62. {tool_compass-2.2.1 → tool_compass-2.3.0}/.env.example +0 -0
  63. {tool_compass-2.2.1 → tool_compass-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  64. {tool_compass-2.2.1 → tool_compass-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  65. {tool_compass-2.2.1 → tool_compass-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  66. {tool_compass-2.2.1 → tool_compass-2.3.0}/LICENSE +0 -0
  67. {tool_compass-2.2.1 → tool_compass-2.3.0}/SHIP_GATE.md +0 -0
  68. {tool_compass-2.2.1 → tool_compass-2.3.0}/_version.py +0 -0
  69. {tool_compass-2.2.1 → tool_compass-2.3.0}/assets/logo.png +0 -0
  70. {tool_compass-2.2.1 → tool_compass-2.3.0}/bootstrap.py +0 -0
  71. {tool_compass-2.2.1 → tool_compass-2.3.0}/compass_config.example.json +0 -0
  72. {tool_compass-2.2.1 → tool_compass-2.3.0}/docker-compose.yml +0 -0
  73. {tool_compass-2.2.1 → tool_compass-2.3.0}/docs/assets/social-preview.png +0 -0
  74. {tool_compass-2.2.1 → tool_compass-2.3.0}/docs/assets/social-preview.svg +0 -0
  75. {tool_compass-2.2.1 → tool_compass-2.3.0}/docs/assets/tool-compass-logo-dark-bg.jpg +0 -0
  76. {tool_compass-2.2.1 → tool_compass-2.3.0}/docs/index.md +0 -0
  77. {tool_compass-2.2.1 → tool_compass-2.3.0}/fly.toml +0 -0
  78. {tool_compass-2.2.1 → tool_compass-2.3.0}/logo.png +0 -0
  79. {tool_compass-2.2.1 → tool_compass-2.3.0}/requirements-dev.txt +0 -0
  80. {tool_compass-2.2.1 → tool_compass-2.3.0}/requirements.txt +0 -0
  81. {tool_compass-2.2.1 → tool_compass-2.3.0}/scripts/check-org-urls.sh +0 -0
  82. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/package-lock.json +0 -0
  83. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/package.json +0 -0
  84. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content/docs/handbook/operations.md +0 -0
  85. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content/docs/handbook/tools.md +0 -0
  86. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/content.config.ts +0 -0
  87. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/pages/index.astro +0 -0
  88. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/site-config.ts +0 -0
  89. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/styles/global.css +0 -0
  90. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/src/styles/starlight-custom.css +0 -0
  91. {tool_compass-2.2.1 → tool_compass-2.3.0}/site/tsconfig.json +0 -0
@@ -0,0 +1,84 @@
1
+ # Git
2
+ .git
3
+ .gitignore
4
+
5
+ # Docker
6
+ Dockerfile
7
+ docker-compose.yml
8
+ .dockerignore
9
+
10
+ # Python
11
+ __pycache__
12
+ *.py[cod]
13
+ *$py.class
14
+ *.so
15
+ .Python
16
+ venv/
17
+ .venv/
18
+ ENV/
19
+ env/
20
+ .eggs/
21
+ *.egg-info/
22
+ *.egg
23
+
24
+ # IDE
25
+ .vscode/
26
+ .idea/
27
+ *.swp
28
+ *.swo
29
+
30
+ # Testing
31
+ .pytest_cache/
32
+ .coverage
33
+ htmlcov/
34
+ .tox/
35
+ .hypothesis/
36
+ .benchmarks/
37
+
38
+ # Build artifacts
39
+ dist/
40
+ build/
41
+ *.manifest
42
+ *.spec
43
+
44
+ # OS files
45
+ .DS_Store
46
+ Thumbs.db
47
+
48
+ # Local development files
49
+ *.local.json
50
+ .env.local
51
+ .env*
52
+
53
+ # Gradio cache
54
+ flagged/
55
+
56
+ # Keep db directory structure but not contents
57
+ db/*.db
58
+ db/*.hnsw
59
+ !db/.gitkeep
60
+
61
+ # Sources that don't belong in the production image (CT-B-004).
62
+ # These are intentionally excluded so a future regression to
63
+ # `COPY . .` doesn't silently ship them.
64
+ tests/
65
+ docs/
66
+ site/
67
+ archive/
68
+ .github/
69
+ .claude/
70
+ # Translation READMEs (re-run on TranslateGemma 12B; not runtime artifacts)
71
+ README.ja.md
72
+ README.zh.md
73
+ README.es.md
74
+ README.fr.md
75
+ README.hi.md
76
+ README.it.md
77
+ README.pt-BR.md
78
+ # Audit / governance docs — not needed at runtime
79
+ SCORECARD.md
80
+ SHIP_GATE.md
81
+ CODE_OF_CONDUCT.md
82
+ CONTRIBUTING.md
83
+ SECURITY.md
84
+ CHANGELOG.md
@@ -0,0 +1,89 @@
1
+ version: 2
2
+ updates:
3
+ # =============================================================================
4
+ # pip — Python runtime + dev dependencies
5
+ # =============================================================================
6
+ - package-ecosystem: "pip"
7
+ directory: "/"
8
+ schedule:
9
+ interval: "monthly"
10
+ open-pull-requests-limit: 3
11
+ groups:
12
+ # Minor + patch only so breaking majors open as SEPARATE PRs.
13
+ # (Avoids the ollama-intern PR #4 incident where 5 breaking majors
14
+ # shipped bundled under a wildcard group.)
15
+ all-pip:
16
+ patterns: ["*"]
17
+ update-types: ["minor", "patch"]
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
+ # =============================================================================
39
+ # github-actions — workflow file action references
40
+ # =============================================================================
41
+ - package-ecosystem: "github-actions"
42
+ directory: "/"
43
+ schedule:
44
+ interval: "monthly"
45
+ open-pull-requests-limit: 3
46
+ groups:
47
+ all-github-actions:
48
+ patterns: ["*"]
49
+ update-types: ["minor", "patch"]
50
+
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
+ # =============================================================================
64
+ # docker — Dockerfile base-image FROM directives (CT-B-005)
65
+ # =============================================================================
66
+ # Pairs with the digest-pinned `FROM python:3.11-slim@sha256:...` in
67
+ # Dockerfile (CT-B-003). Without this ecosystem entry the digest pin
68
+ # would become a stale anchor with no machine-readable refresh path.
69
+ - package-ecosystem: "docker"
70
+ directory: "/"
71
+ schedule:
72
+ interval: "monthly"
73
+ open-pull-requests-limit: 2
74
+ groups:
75
+ all-docker:
76
+ patterns: ["*"]
77
+ 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"
@@ -35,9 +35,16 @@ on:
35
35
  - cron: '0 9 * * 1,3,5'
36
36
  workflow_dispatch:
37
37
 
38
- concurrency:
39
- group: ${{ github.workflow }}-${{ github.ref }}
40
- cancel-in-progress: true
38
+ # NOTE: concurrency is intentionally NOT set at the workflow level.
39
+ # A workflow-level cancel-in-progress would cancel the whole run when a
40
+ # new push lands on the same ref — including the pages-deploy job that
41
+ # carries its own concurrency: pages / cancel-in-progress: false. The
42
+ # job-level setting cannot override workflow-level cancellation because
43
+ # the entire run is cancelled before per-job concurrency takes effect.
44
+ # Each test/lint/integration/docker job below carries its own
45
+ # concurrency block scoped to PR refs only (push events on main are
46
+ # never cancelled). The pages-build / pages-deploy jobs keep their
47
+ # dedicated concurrency: pages group with cancel-in-progress: false.
41
48
 
42
49
  # Default least-privilege; individual jobs that need more elevate explicitly.
43
50
  permissions:
@@ -47,6 +54,13 @@ jobs:
47
54
  lint:
48
55
  name: Org URL sanity check
49
56
  runs-on: ubuntu-latest
57
+ # CT-B-011: lint is a sub-30-second shell check; 5 min is generous.
58
+ timeout-minutes: 5
59
+ # PR-scoped concurrency: a newer push to the same PR cancels the
60
+ # older run, but pushes to main never cancel themselves.
61
+ concurrency:
62
+ group: lint-${{ github.event.pull_request.number || github.run_id }}
63
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
50
64
  permissions:
51
65
  contents: read
52
66
  steps:
@@ -56,6 +70,22 @@ jobs:
56
70
  - name: Check for stale org/user URLs
57
71
  run: bash scripts/check-org-urls.sh
58
72
 
73
+ - name: Set up Python (pre-commit)
74
+ uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
75
+ with:
76
+ python-version: '3.11'
77
+
78
+ # CT-B-018: run the pre-commit hooks (ruff format + ruff check +
79
+ # standard hygiene + gitleaks). Same hooks the contributor runs
80
+ # locally; this is the CI-side enforcement. Soft-fail for the first
81
+ # cycle to surface formatting drift without blocking PRs while
82
+ # contributors install pre-commit locally.
83
+ # TODO(swarm): flip continue-on-error to false after one full release
84
+ # cycle so the pre-commit gate becomes blocking.
85
+ - name: Run pre-commit hooks
86
+ continue-on-error: true
87
+ uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
88
+
59
89
  # CDS-FT-001: SCORECARD drift guard. Soft-fail for now — shipcheck
60
90
  # markdown output is still settling. Flip continue-on-error to false
61
91
  # once the format stabilizes.
@@ -69,9 +99,32 @@ jobs:
69
99
  echo "Makefile verify-scorecard target missing — skipping"
70
100
  fi
71
101
 
102
+ # CT-B-008 cross-domain: verify that /metrics emits the expected
103
+ # Four Golden Signals surface. Soft-fail because the saturation
104
+ # gauges land in BE-B-002 (backend domain); flip to fatal once that
105
+ # work merges.
106
+ - name: Verify metrics surface (Four Golden Signals)
107
+ continue-on-error: true
108
+ run: |
109
+ if [ -f Makefile ] && grep -q "^verify-metrics:" Makefile; then
110
+ python -m pip install --upgrade pip
111
+ pip install -r requirements.txt || true
112
+ make verify-metrics || true
113
+ else
114
+ echo "Makefile verify-metrics target missing — skipping"
115
+ fi
116
+
72
117
  test:
73
118
  name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
74
119
  runs-on: ${{ matrix.os }}
120
+ # CT-B-011: per-matrix-cell budget. pytest has its own 30s per-test cap
121
+ # (pyproject.toml [tool.pytest.ini_options]); 15 min envelope covers
122
+ # install + coverage + pip-audit with headroom for a slow runner.
123
+ timeout-minutes: 15
124
+ # PR-scoped concurrency (see workflow-top NOTE).
125
+ concurrency:
126
+ group: test-${{ matrix.python-version }}-${{ github.event.pull_request.number || github.run_id }}
127
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
75
128
  permissions:
76
129
  contents: read
77
130
 
@@ -116,6 +169,9 @@ jobs:
116
169
  name: junit-reports-${{ matrix.python-version }}
117
170
  path: junit-${{ matrix.python-version }}.xml
118
171
  if-no-files-found: warn
172
+ # CT-B-012: JUnit reports are short-lived diagnostic data — only
173
+ # useful for the most recent few runs. Default 90d is wasted.
174
+ retention-days: 14
119
175
 
120
176
  - name: Run tests with coverage
121
177
  if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
@@ -142,6 +198,9 @@ jobs:
142
198
  name: pip-audit-report
143
199
  path: pip-audit-report.json
144
200
  if-no-files-found: ignore
201
+ # CT-B-012: CVE diagnostic snapshot — 14d aligns with the JUnit
202
+ # retention and is plenty of window for incident response.
203
+ retention-days: 14
145
204
 
146
205
  - name: Audit dependencies for vulnerabilities
147
206
  if: matrix.python-version == '3.11' && matrix.os == 'ubuntu-latest'
@@ -157,6 +216,9 @@ jobs:
157
216
  name: Nightly Hypothesis fuzz
158
217
  if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
159
218
  runs-on: ubuntu-latest
219
+ # CT-B-011: Hypothesis nightly profile is deliberately slow — 45 min cap
220
+ # is wider than the test job because fuzz examples are unbounded.
221
+ timeout-minutes: 45
160
222
  permissions:
161
223
  contents: read
162
224
  issues: write
@@ -184,9 +246,10 @@ jobs:
184
246
 
185
247
  - name: Open tracking issue on failure
186
248
  if: failure() && steps.fuzz.conclusion == 'failure'
187
- # TODO(swarm): pin actions/github-script to SHA once nightly-fuzz run
188
- # history confirms the job shape is stable.
189
- uses: actions/github-script@v7
249
+ # SHA-pinned per SLSA L3 supply-chain hygiene (CT-B-001). nightly-fuzz
250
+ # carries issues:write, so a compromised v7 mutable tag would inherit
251
+ # issue-creation capability. Bump via dependabot's github-actions sweep.
252
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
190
253
  with:
191
254
  script: |
192
255
  const title = `[nightly-fuzz] Hypothesis failures on ${context.sha}`;
@@ -212,6 +275,13 @@ jobs:
212
275
  name: Integration Tests
213
276
  runs-on: ubuntu-latest
214
277
  needs: test # Only run after unit tests pass
278
+ # CT-B-011: Ollama install + nomic-embed-text pull + integration suite
279
+ # dominates; 25 min envelope covers the cold-start pull path.
280
+ timeout-minutes: 25
281
+ # PR-scoped concurrency (see workflow-top NOTE).
282
+ concurrency:
283
+ group: integration-${{ github.event.pull_request.number || github.run_id }}
284
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
215
285
  permissions:
216
286
  contents: read
217
287
 
@@ -271,6 +341,12 @@ jobs:
271
341
  name: Docker Build
272
342
  runs-on: ubuntu-latest
273
343
  needs: test
344
+ # CT-B-011: docker buildx + gha cache; 20 min covers a cold-cache build.
345
+ timeout-minutes: 20
346
+ # PR-scoped concurrency (see workflow-top NOTE).
347
+ concurrency:
348
+ group: docker-${{ github.event.pull_request.number || github.run_id }}
349
+ cancel-in-progress: ${{ github.event_name == 'pull_request' }}
274
350
  permissions:
275
351
  contents: read
276
352
 
@@ -299,6 +375,8 @@ jobs:
299
375
  name: Build site
300
376
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
301
377
  runs-on: ubuntu-latest
378
+ # CT-B-011: Starlight npm ci + Astro build; 10 min is generous.
379
+ timeout-minutes: 10
302
380
  permissions:
303
381
  contents: read
304
382
  steps:
@@ -308,6 +386,12 @@ jobs:
308
386
  with:
309
387
  node-version: 22
310
388
 
389
+ # CT-B-016: read repo-level Pages configuration so the build is not
390
+ # silently dependent on Settings → Pages UI state. configure-pages
391
+ # is idempotent and exposes the base path to downstream steps.
392
+ - name: Configure Pages
393
+ uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
394
+
311
395
  - name: Install site dependencies
312
396
  working-directory: site
313
397
  run: npm ci
@@ -325,6 +409,9 @@ jobs:
325
409
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
326
410
  needs: pages-build
327
411
  runs-on: ubuntu-latest
412
+ # CT-B-011: deploy is a metadata operation against the Pages backend;
413
+ # 5 min is the fail-fast budget.
414
+ timeout-minutes: 5
328
415
  # Dedicated concurrency group so a long deploy is never cancelled by a
329
416
  # newer CI run (CDS-A-012).
330
417
  concurrency:
@@ -20,6 +20,10 @@ jobs:
20
20
  build:
21
21
  name: Build package
22
22
  runs-on: ubuntu-latest
23
+ # CT-B-011: fail-fast budgets per SRE 'fail fast' (Google SRE Book ch.21).
24
+ # Default GitHub timeout is 360 min; a wedged dependency install or twine
25
+ # check is well outside the legitimate envelope at 10 min.
26
+ timeout-minutes: 10
23
27
 
24
28
  steps:
25
29
  - name: Checkout repository
@@ -46,14 +50,26 @@ jobs:
46
50
  with:
47
51
  name: dist
48
52
  path: dist/
53
+ # CT-B-012: dist/ is an ephemeral build-to-publish handoff; the
54
+ # canonical artifacts live at PyPI + GHCR after publish completes.
55
+ # Keep the workflow copy around 7 days for incident-response replays.
56
+ retention-days: 7
49
57
 
50
58
  publish-pypi:
51
59
  name: Publish to PyPI
52
60
  runs-on: ubuntu-latest
53
61
  needs: build
54
62
  environment: pypi
63
+ # CT-B-011: PyPI upload + propagation; 10 min covers retries.
64
+ timeout-minutes: 10
55
65
  permissions:
56
66
  id-token: write
67
+ # CT-B-009: attestations:write enables SLSA provenance attestations on
68
+ # the published wheel + sdist via pypa/gh-action-pypi-publish v1.13+.
69
+ # Combined with `attestations: true` below, PyPI surfaces a verifiable
70
+ # build attestation tied to this commit + workflow run.
71
+ attestations: write
72
+ contents: read
57
73
 
58
74
  steps:
59
75
  - name: Download build artifacts
@@ -64,6 +80,12 @@ jobs:
64
80
 
65
81
  - name: Publish to PyPI
66
82
  uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
83
+ with:
84
+ # CT-B-009: emit SLSA build provenance attestations for the wheel
85
+ # and sdist. PyPI verifies the attestation against the workflow's
86
+ # OIDC identity; downstream consumers can audit the chain from a
87
+ # specific commit -> workflow run -> published artifact.
88
+ attestations: true
67
89
 
68
90
  docker:
69
91
  name: Publish to GHCR
@@ -71,9 +93,16 @@ jobs:
71
93
  # PyPI ships first (CDS-B-005). If PyPI fails, Docker must not ship a
72
94
  # half-release; if PyPI succeeds, Docker becomes the recoverable tail.
73
95
  needs: publish-pypi
96
+ # CT-B-011: multi-arch buildx (linux/amd64 + linux/arm64) + push to GHCR
97
+ # is the longest leg; 30 min covers the warm cache path with headroom.
98
+ timeout-minutes: 30
74
99
  permissions:
75
100
  contents: read
76
101
  packages: write
102
+ # CT-B-009: id-token + attestations enable buildx SLSA provenance
103
+ # mode=max on the image manifest below.
104
+ id-token: write
105
+ attestations: write
77
106
 
78
107
  steps:
79
108
  - name: Checkout repository
@@ -99,8 +128,10 @@ jobs:
99
128
 
100
129
  # QEMU is required to cross-build linux/arm64 from an amd64 runner.
101
130
  - name: Set up QEMU
102
- # TODO(swarm): pin docker/setup-qemu-action by SHA on next dependabot sweep.
103
- uses: docker/setup-qemu-action@v3
131
+ # SHA-pinned per SLSA L3 supply-chain hygiene (CT-B-002). publish job
132
+ # carries packages:write — a compromised mutable v3 tag would inherit
133
+ # GHCR write capability during a release window. Bump via dependabot.
134
+ uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
104
135
 
105
136
  - name: Set up Docker Buildx
106
137
  uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
@@ -115,11 +146,21 @@ jobs:
115
146
  labels: ${{ steps.meta.outputs.labels }}
116
147
  cache-from: type=gha
117
148
  cache-to: type=gha,mode=max
149
+ # CT-B-009: SLSA provenance mode=max emits buildx provenance
150
+ # attestation (including all input source digests) attached to the
151
+ # image manifest. sbom: true emits an SPDX SBOM attestation in the
152
+ # same manifest. Both consumable via `cosign verify-attestation`
153
+ # or `docker buildx imagetools inspect`.
154
+ provenance: mode=max
155
+ sbom: true
118
156
 
119
157
  release-smoke:
120
158
  name: Release smoke test
121
159
  runs-on: ubuntu-latest
122
160
  needs: [publish-pypi, docker]
161
+ # CT-B-011: PyPI propagation retry loop + docker pull + version handshake.
162
+ # 15 min is the propagation-retry window plus headroom.
163
+ timeout-minutes: 15
123
164
  permissions:
124
165
  contents: read
125
166
  packages: read
@@ -141,14 +182,22 @@ jobs:
141
182
  run: |
142
183
  set -euo pipefail
143
184
  VERSION="${{ steps.ver.outputs.version }}"
144
- # PyPI propagation is fast but not instant; retry briefly.
185
+ # PyPI propagation is fast but not instant; retry briefly. If no
186
+ # iteration succeeds the loop falls through and the subsequent
187
+ # `tool-compass --version` invocation fails with a clear "command
188
+ # not found", which is the desired loud failure mode (CT-B-014).
189
+ install_ok=0
145
190
  for _ in $(seq 1 12); do
146
191
  if pip install "tool-compass==${VERSION}"; then
192
+ install_ok=1
147
193
  break
148
194
  fi
149
195
  sleep 10
150
196
  done
151
- pip install "tool-compass==${VERSION}"
197
+ if [ "$install_ok" -ne 1 ]; then
198
+ echo "::error::PyPI smoke failed: tool-compass==${VERSION} did not become installable in the propagation window"
199
+ exit 1
200
+ fi
152
201
  OUT="$(tool-compass --version 2>&1 || true)"
153
202
  echo "tool-compass --version → $OUT"
154
203
  echo "$OUT" | grep -Fq "${VERSION}" || {