relay-shell 0.4.0__tar.gz → 0.5.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 (245) hide show
  1. {relay_shell-0.4.0 → relay_shell-0.5.0}/.env.example +8 -3
  2. relay_shell-0.5.0/.github/workflows/ci-gate.yml +70 -0
  3. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/ci.yml +45 -11
  4. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/codeql.yml +14 -5
  5. relay_shell-0.5.0/.github/workflows/copilot-setup-steps.yml +42 -0
  6. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/dependency-review.yml +9 -0
  7. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/docs.yml +9 -6
  8. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/gitleaks.yml +9 -0
  9. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/nightly-fuzz.yml +5 -2
  10. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/pip-audit.yml +28 -8
  11. relay_shell-0.5.0/.github/workflows/release-rehearsal.yml +115 -0
  12. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/release.yml +15 -7
  13. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/workflows/sbom.yml +33 -22
  14. {relay_shell-0.4.0 → relay_shell-0.5.0}/.gitignore +8 -0
  15. {relay_shell-0.4.0 → relay_shell-0.5.0}/.pre-commit-config.yaml +1 -1
  16. {relay_shell-0.4.0 → relay_shell-0.5.0}/BACKLOG.md +43 -4
  17. {relay_shell-0.4.0 → relay_shell-0.5.0}/CHANGELOG.md +67 -1
  18. {relay_shell-0.4.0 → relay_shell-0.5.0}/CONTRIBUTING.md +43 -7
  19. {relay_shell-0.4.0 → relay_shell-0.5.0}/PKG-INFO +8 -7
  20. {relay_shell-0.4.0 → relay_shell-0.5.0}/README.md +6 -4
  21. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/install-edge.sh +21 -4
  22. relay_shell-0.5.0/deploy/install.sh +208 -0
  23. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0001-runtime-and-sdk.md +8 -3
  24. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0005-codebase-validation.md +4 -3
  25. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0006-seccomp-notify-audit-channel.md +8 -0
  26. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0007-audit-hash-chain.md +4 -3
  27. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0012-cancel-safe-session-teardown.md +4 -3
  28. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0013-open-by-default-with-unsafe-notices.md +4 -2
  29. relay_shell-0.5.0/docs/adr/0014-seccomp-launch-and-install-recovery.md +180 -0
  30. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/README.md +7 -7
  31. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/architecture.md +29 -16
  32. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/audit-shipper.md +40 -4
  33. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/auth.md +8 -5
  34. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/deployment.md +262 -37
  35. relay_shell-0.5.0/docs/index.md +58 -0
  36. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/release-trust.md +10 -3
  37. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/runbook.md +214 -37
  38. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/tools.md +29 -21
  39. {relay_shell-0.4.0 → relay_shell-0.5.0}/mkdocs.yml +4 -2
  40. {relay_shell-0.4.0 → relay_shell-0.5.0}/pyproject.toml +3 -4
  41. relay_shell-0.5.0/renovate.json5 +203 -0
  42. relay_shell-0.5.0/requirements/README.md +36 -0
  43. relay_shell-0.5.0/requirements/development-py3.12.txt +1636 -0
  44. relay_shell-0.5.0/requirements/development-py3.14.txt +1632 -0
  45. relay_shell-0.5.0/requirements/runtime-py3.12.txt +598 -0
  46. relay_shell-0.5.0/requirements/runtime-py3.14.txt +595 -0
  47. relay_shell-0.5.0/requirements-audit.in +2 -0
  48. relay_shell-0.5.0/requirements-audit.txt +442 -0
  49. {relay_shell-0.4.0 → relay_shell-0.5.0}/requirements-build.txt +13 -13
  50. relay_shell-0.5.0/requirements-docs.txt +5 -0
  51. {relay_shell-0.4.0 → relay_shell-0.5.0}/requirements-sbom.txt +51 -51
  52. relay_shell-0.5.0/requirements.txt +21 -0
  53. relay_shell-0.5.0/scripts/check_ci_checks.py +61 -0
  54. relay_shell-0.5.0/scripts/installer_transaction.py +219 -0
  55. relay_shell-0.5.0/scripts/reconcile_runtime.py +77 -0
  56. relay_shell-0.5.0/scripts/rehearse_install.py +532 -0
  57. relay_shell-0.5.0/scripts/rehearse_release.py +173 -0
  58. relay_shell-0.5.0/scripts/rehearse_upgrade.py +756 -0
  59. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/__init__.py +1 -1
  60. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/auth/oauth.py +25 -1
  61. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/redaction.py +33 -1
  62. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/seccomp.py +129 -28
  63. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/sessions.py +6 -2
  64. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/shelltools.py +16 -3
  65. relay_shell-0.5.0/tests/fixtures/upgrade-v0.3/README.md +53 -0
  66. relay_shell-0.5.0/tests/fixtures/upgrade-v0.3/advisory-description.txt +1 -0
  67. relay_shell-0.5.0/tests/fixtures/upgrade-v0.3/baseline.json +13 -0
  68. relay_shell-0.5.0/tests/fixtures/upgrade-v0.3/runtime.in +10 -0
  69. relay_shell-0.5.0/tests/fixtures/upgrade-v0.3/runtime.lock +582 -0
  70. relay_shell-0.5.0/tests/test_ci_checks.py +68 -0
  71. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_ci_gate.py +1 -0
  72. relay_shell-0.5.0/tests/test_config_template.py +31 -0
  73. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_deploy_packaging.py +67 -0
  74. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_install_edge_validation.py +62 -0
  75. relay_shell-0.5.0/tests/test_install_rehearsal.py +48 -0
  76. relay_shell-0.5.0/tests/test_installer_transaction.py +106 -0
  77. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_oauth_store.py +19 -0
  78. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_redaction.py +6 -0
  79. relay_shell-0.5.0/tests/test_release_admission.py +170 -0
  80. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_release_workflows.py +34 -2
  81. relay_shell-0.5.0/tests/test_runtime_inventory.py +33 -0
  82. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_seccomp.py +107 -2
  83. relay_shell-0.5.0/tests/test_upgrade_rehearsal.py +102 -0
  84. relay_shell-0.5.0/tests/test_workflow_hygiene.py +106 -0
  85. relay_shell-0.4.0/.github/workflows/ci-gate.yml +0 -109
  86. relay_shell-0.4.0/.hypothesis/.gitignore +0 -9
  87. relay_shell-0.4.0/.hypothesis/constants/03552e8feb87dd21 +0 -4
  88. relay_shell-0.4.0/.hypothesis/constants/376080bff0f77cd7 +0 -4
  89. relay_shell-0.4.0/.hypothesis/constants/488bf38869fa6d62 +0 -4
  90. relay_shell-0.4.0/.hypothesis/constants/4b4e4855dd69cf11 +0 -4
  91. relay_shell-0.4.0/.hypothesis/constants/4e8204b0a01bc033 +0 -4
  92. relay_shell-0.4.0/.hypothesis/constants/4f785f577a444b6a +0 -4
  93. relay_shell-0.4.0/.hypothesis/constants/4ffce8883f0431d6 +0 -4
  94. relay_shell-0.4.0/.hypothesis/constants/5a149f13c700550f +0 -4
  95. relay_shell-0.4.0/.hypothesis/constants/5c2a3b580297d057 +0 -4
  96. relay_shell-0.4.0/.hypothesis/constants/7f7fe1194ef1e944 +0 -4
  97. relay_shell-0.4.0/.hypothesis/constants/9a25a08ade8424e9 +0 -4
  98. relay_shell-0.4.0/.hypothesis/constants/9bb33f940a88174f +0 -4
  99. relay_shell-0.4.0/.hypothesis/constants/a72b8354efb4636f +0 -4
  100. relay_shell-0.4.0/.hypothesis/constants/aa40616331558629 +0 -4
  101. relay_shell-0.4.0/.hypothesis/constants/b6e158e4328d9b9e +0 -4
  102. relay_shell-0.4.0/.hypothesis/constants/b98a154a8f88cbf3 +0 -4
  103. relay_shell-0.4.0/.hypothesis/constants/be5269261b77f6de +0 -4
  104. relay_shell-0.4.0/.hypothesis/constants/cf234c289a36e8fc +0 -4
  105. relay_shell-0.4.0/.hypothesis/constants/d78536b0c96f72a7 +0 -4
  106. relay_shell-0.4.0/.hypothesis/constants/daf0a2d5be3bdb3b +0 -4
  107. relay_shell-0.4.0/.hypothesis/constants/e07beacdd1b67d6e +0 -4
  108. relay_shell-0.4.0/.hypothesis/constants/ea23723ef0e64c17 +0 -4
  109. relay_shell-0.4.0/.hypothesis/constants/f0de1053c6198680 +0 -4
  110. relay_shell-0.4.0/.hypothesis/constants/f58b7c14550debdb +0 -4
  111. relay_shell-0.4.0/.hypothesis/unicode_data/16.0.0/charmap.json.gz +0 -0
  112. relay_shell-0.4.0/deploy/install.sh +0 -83
  113. relay_shell-0.4.0/docs/index.md +0 -40
  114. relay_shell-0.4.0/renovate.json5 +0 -26
  115. relay_shell-0.4.0/requirements-docs.txt +0 -5
  116. relay_shell-0.4.0/requirements.txt +0 -21
  117. relay_shell-0.4.0/tests/test_workflow_hygiene.py +0 -65
  118. {relay_shell-0.4.0 → relay_shell-0.5.0}/.claude/settings.json +0 -0
  119. {relay_shell-0.4.0 → relay_shell-0.5.0}/.editorconfig +0 -0
  120. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/CODEOWNERS +0 -0
  121. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  122. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  123. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/ISSUE_TEMPLATE/security.md +0 -0
  124. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  125. {relay_shell-0.4.0 → relay_shell-0.5.0}/.github/copilot-instructions.md +0 -0
  126. {relay_shell-0.4.0 → relay_shell-0.5.0}/.gitleaks.toml +0 -0
  127. {relay_shell-0.4.0 → relay_shell-0.5.0}/AGENTS.md +0 -0
  128. {relay_shell-0.4.0 → relay_shell-0.5.0}/CLAUDE.md +0 -0
  129. {relay_shell-0.4.0 → relay_shell-0.5.0}/CODE_OF_CONDUCT.md +0 -0
  130. {relay_shell-0.4.0 → relay_shell-0.5.0}/LICENSE +0 -0
  131. {relay_shell-0.4.0 → relay_shell-0.5.0}/NOTICE +0 -0
  132. {relay_shell-0.4.0 → relay_shell-0.5.0}/SECURITY.md +0 -0
  133. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/00-inventory.md +0 -0
  134. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/01-baseline.md +0 -0
  135. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/02-security-findings.md +0 -0
  136. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/03-final-report.md +0 -0
  137. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-05-27-engagement.md +0 -0
  138. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-06-01-engagement.md +0 -0
  139. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-06-21-adversarial-engagement.md +0 -0
  140. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-06-21-engagement.md +0 -0
  141. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-07-15-adversarial-engagement.md +0 -0
  142. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-07-15-engagement.md +0 -0
  143. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-07-15-release-v0.3.0.md +0 -0
  144. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-08-04-backlog-followup.md +0 -0
  145. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-08-04-engagement.md +0 -0
  146. {relay_shell-0.4.0 → relay_shell-0.5.0}/audit/2026-10-14-engagement.md +0 -0
  147. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/Caddyfile +0 -0
  148. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/README.md +0 -0
  149. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/approved-resets.json.example +0 -0
  150. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/audit-evidence.env.example +0 -0
  151. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/relay-audit-evidence-sync +0 -0
  152. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/relay-audit-evidence.py +0 -0
  153. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/relay-audit-evidence.service +0 -0
  154. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/evidence/relay-audit-evidence.timer +0 -0
  155. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/logrotate/relay-shell +0 -0
  156. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/PACKAGE.lock +0 -0
  157. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/README.md +0 -0
  158. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/nftables.conf.snippet +0 -0
  159. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/prometheus-node-exporter.service.d/override.conf +0 -0
  160. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/relay-health-metrics.py +0 -0
  161. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/relay-health-metrics.service +0 -0
  162. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/relay-health-metrics.timer +0 -0
  163. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/monitoring/relay-monitoring.env.example +0 -0
  164. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/release-signing.pub +0 -0
  165. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/systemd/relay-shell.service +0 -0
  166. {relay_shell-0.4.0 → relay_shell-0.5.0}/deploy/systemd/relay-shell.service.d/hardening.conf +0 -0
  167. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0002-no-sandbox-full-access.md +0 -0
  168. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0003-tiered-authority.md +0 -0
  169. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0004-edge-tls-automation.md +0 -0
  170. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0008-operating-guidance-prompt.md +0 -0
  171. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0009-tier3-confirmation-broker.md +0 -0
  172. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0010-rollback-verify-broker.md +0 -0
  173. {relay_shell-0.4.0 → relay_shell-0.5.0}/docs/adr/0011-windows-openssh-powershell.md +0 -0
  174. {relay_shell-0.4.0 → relay_shell-0.5.0}/requirements-build.in +0 -0
  175. {relay_shell-0.4.0 → relay_shell-0.5.0}/requirements-sbom.in +0 -0
  176. {relay_shell-0.4.0 → relay_shell-0.5.0}/scripts/healthcheck.sh +0 -0
  177. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/__main__.py +0 -0
  178. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/audit.py +0 -0
  179. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/auth/__init__.py +0 -0
  180. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/auth/admin.py +0 -0
  181. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/auth/resource.py +0 -0
  182. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/broker.py +0 -0
  183. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/config.py +0 -0
  184. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/errors.py +0 -0
  185. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/inventory.py +0 -0
  186. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/metrics.py +0 -0
  187. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/notices.py +0 -0
  188. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/patterns.py +0 -0
  189. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/policy.py +0 -0
  190. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/py.typed +0 -0
  191. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/server.py +0 -0
  192. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/sshpool.py +0 -0
  193. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/util.py +0 -0
  194. {relay_shell-0.4.0 → relay_shell-0.5.0}/src/relay_shell/verifier.py +0 -0
  195. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/conftest.py +0 -0
  196. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/helpers.py +0 -0
  197. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_a14_rs.py +0 -0
  198. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_a14_t1_gaps.py +0 -0
  199. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_admission_guards.py +0 -0
  200. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_audit.py +0 -0
  201. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_audit_lows.py +0 -0
  202. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_audit_tail_tool.py +0 -0
  203. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_authenticated_audit.py +0 -0
  204. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_broker.py +0 -0
  205. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_broker_client_binding.py +0 -0
  206. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_cancellation_audit.py +0 -0
  207. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_classifier_v12.py +0 -0
  208. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_classifier_v16.py +0 -0
  209. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_config.py +0 -0
  210. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_config_hygiene.py +0 -0
  211. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_dependency_floors.py +0 -0
  212. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_edge_routing.py +0 -0
  213. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_errors.py +0 -0
  214. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_evidence_deploy.py +0 -0
  215. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_fuzz.py +0 -0
  216. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_healthcheck.py +0 -0
  217. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_inventory.py +0 -0
  218. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_logrotate_config.py +0 -0
  219. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_main.py +0 -0
  220. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_metrics.py +0 -0
  221. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_notices.py +0 -0
  222. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_oauth.py +0 -0
  223. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_oauth_approval.py +0 -0
  224. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_oauth_resource.py +0 -0
  225. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_oauth_reuse.py +0 -0
  226. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_patterns.py +0 -0
  227. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_policy.py +0 -0
  228. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_prompts.py +0 -0
  229. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_redaction_operators.py +0 -0
  230. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_redaction_v16.py +0 -0
  231. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_resources.py +0 -0
  232. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_root_execution_contract.py +0 -0
  233. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_server.py +0 -0
  234. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_session_secret_audit.py +0 -0
  235. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_sessions.py +0 -0
  236. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_sessions_cancel.py +0 -0
  237. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_shell.py +0 -0
  238. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_ssh_fanout_tool.py +0 -0
  239. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_ssh_integration.py +0 -0
  240. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_ssh_keyscan_tool.py +0 -0
  241. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_sshpool_unit.py +0 -0
  242. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_stdio_e2e.py +0 -0
  243. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_tool_wrappers.py +0 -0
  244. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_util.py +0 -0
  245. {relay_shell-0.4.0 → relay_shell-0.5.0}/tests/test_verifier.py +0 -0
@@ -20,6 +20,7 @@ RELAY_SHELL_MAX_OUTPUT_HARD=1048576
20
20
  # scanned partially. Raise it only if you really pipe multi-MiB payloads.
21
21
  # 1024..134217728.
22
22
  RELAY_SHELL_MAX_INPUT=2097152
23
+ # Default for shell_exec and ssh_exec when the call omits timeout.
23
24
  RELAY_SHELL_DEFAULT_TIMEOUT=60
24
25
  RELAY_SHELL_MAX_TIMEOUT=900
25
26
  RELAY_SHELL_MAX_SESSIONS=64
@@ -69,9 +70,10 @@ RELAY_SHELL_AUDIT_INTENT=false
69
70
  RELAY_SHELL_AUDIT_SESSION_INPUT=redacted
70
71
  # Tamper-evident audit (ADR 0007). When true, each record carries seq/prev/chain
71
72
  # so an edit, insertion, reorder, or interior deletion of the on-disk log is
72
- # detectable with `relay-shell --verify-audit` (head-truncation via
73
- # --require-genesis; tail-truncation and cross-file continuity are the off-host
74
- # shipper's job). Default off keeps the record byte-identical. Requires
73
+ # detectable with `relay-shell --verify-audit`. The verifier requires genesis
74
+ # by default; use --segment only for an explicitly partial segment.
75
+ # Tail-truncation and cross-file continuity need an off-host anchor/shipper.
76
+ # Default off keeps the record byte-identical. Requires
75
77
  # RELAY_SHELL_AUDIT_FORMAT=jsonl. Enable on a freshly rotated log for a clean chain.
76
78
  RELAY_SHELL_AUDIT_CHAIN=false
77
79
 
@@ -99,6 +101,9 @@ RELAY_SHELL_SSH_CONFIG=~/.ssh/config
99
101
  RELAY_SHELL_INVENTORY=
100
102
  # strict | accept-new | ignore
101
103
  RELAY_SHELL_SSH_KNOWN_HOSTS=accept-new
104
+ # Allow a caller to select weaker host verification than the default. Set false
105
+ # to refuse weaker per-call overrides; stronger overrides remain available.
106
+ RELAY_SHELL_SSH_ALLOW_WEAKER_KNOWN_HOSTS=true
102
107
  RELAY_SHELL_SSH_CONNECT_TIMEOUT=10
103
108
  RELAY_SHELL_SSH_KEEPALIVE=30
104
109
  # Idle eviction for cached SSH connections (seconds). The pool consults
@@ -0,0 +1,70 @@
1
+ name: CI aggregate
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ permissions:
7
+ checks: read
8
+ contents: read
9
+
10
+
11
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
12
+ concurrency:
13
+ group: >-
14
+ ci-gate-${{ github.workflow }}-${{ github.event_name }}-${{
15
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
16
+ }}-${{ github.run_attempt }}
17
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
18
+
19
+ jobs:
20
+ ci-success:
21
+ name: ci-success
22
+ if: always()
23
+ runs-on: ubuntu-latest
24
+ timeout-minutes: 30
25
+ env:
26
+ TARGET_SHA: ${{ github.event.pull_request.head.sha }}
27
+ REQUIRED_CHECKS: |-
28
+ Build (MkDocs Material)
29
+ check (py3.12)
30
+ check (py3.14)
31
+ gitleaks (secret scan)
32
+ pip-audit (CVE scan)
33
+ dependency-review
34
+ Analyze (python) (python)
35
+ Analyze (actions)
36
+ # Empty by design. Add an exact check name here only when a skipped
37
+ # result is an explicitly valid outcome for that protected dependency.
38
+ ALLOWED_SKIPPED_CHECKS: ""
39
+ steps:
40
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
41
+ with:
42
+ persist-credentials: false
43
+ - name: Require protected checks to pass
44
+ env:
45
+ GH_TOKEN: ${{ github.token }}
46
+ shell: bash
47
+ run: |
48
+ set -euo pipefail
49
+ snapshot=$(mktemp)
50
+ trap 'rm -f "$snapshot"' EXIT
51
+ deadline=$((SECONDS + 1500))
52
+ while :; do
53
+ # One paginated snapshot per poll, rather than one API call per name.
54
+ # An API failure is a failure, never an empty/successful snapshot.
55
+ gh api --method GET --paginate --slurp \
56
+ "repos/${GITHUB_REPOSITORY}/commits/${TARGET_SHA}/check-runs" \
57
+ -f filter=all -F per_page=100 > "$snapshot"
58
+ result=0
59
+ python3 scripts/check_ci_checks.py < "$snapshot" || result=$?
60
+ case "$result" in
61
+ 0) exit 0 ;;
62
+ 2) ;;
63
+ *) echo '::error::Protected dependency failed or snapshot invalid'; exit 1 ;;
64
+ esac
65
+ if [ "$SECONDS" -ge "$deadline" ]; then
66
+ echo '::error::Timed out waiting for protected dependencies'
67
+ exit 1
68
+ fi
69
+ sleep 10
70
+ done
@@ -8,20 +8,33 @@ on:
8
8
  permissions:
9
9
  contents: read
10
10
 
11
+
12
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
13
+ concurrency:
14
+ group: >-
15
+ ci-${{ github.workflow }}-${{ github.event_name }}-${{
16
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
17
+ }}-${{ github.run_attempt }}
18
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
19
+
11
20
  jobs:
12
21
  check:
13
22
  permissions:
14
23
  contents: read
15
24
  timeout-minutes: 20
16
- runs-on: ubuntu-latest
25
+ runs-on: ${{ matrix.os }}
17
26
  strategy:
18
- # Surface every Python version's result so a single 3.14 wheel gap
19
- # does not mask a 3.13 regression. The package floor is `>=3.12` in
20
- # pyproject.toml; the matrix covers the floor plus the two newer
21
- # interpreters in active CPython support.
27
+ # Qualify the native Python minor for each supported Ubuntu LTS.
28
+ # Both profiles must pass independently, including binary-only lock admission.
22
29
  fail-fast: false
23
30
  matrix:
24
- python-version: ["3.12", "3.13", "3.14"]
31
+ include:
32
+ - os: ubuntu-24.04
33
+ os-version: "24.04"
34
+ python-version: "3.12"
35
+ - os: ubuntu-26.04
36
+ os-version: "26.04"
37
+ python-version: "3.14"
25
38
  name: check (py${{ matrix.python-version }})
26
39
  steps:
27
40
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -32,11 +45,29 @@ jobs:
32
45
  with:
33
46
  python-version: ${{ matrix.python-version }}
34
47
  cache: "pip"
48
+ cache-dependency-path: |
49
+ requirements/development-py${{ matrix.python-version }}.txt
50
+
51
+ - name: Verify the qualified OS and Python profile
52
+ env:
53
+ EXPECTED_UBUNTU: ${{ matrix.os-version }}
54
+ EXPECTED_PYTHON: ${{ matrix.python-version }}
55
+ run: |
56
+ python - <<'PY'
57
+ import os, platform, sys
58
+ release = platform.freedesktop_os_release()
59
+ actual_python = f"{sys.version_info.major}.{sys.version_info.minor}"
60
+ if release["ID"] != "ubuntu" or release["VERSION_ID"] != os.environ["EXPECTED_UBUNTU"]:
61
+ raise SystemExit(f"Unexpected OS profile: {release}")
62
+ if actual_python != os.environ["EXPECTED_PYTHON"]:
63
+ raise SystemExit(f"Unexpected Python profile: {actual_python}")
64
+ PY
35
65
 
36
66
  - name: Install
37
67
  run: |
38
- python -m pip install --upgrade pip
39
- pip install -c requirements.txt -e ".[dev]"
68
+ python -m pip install --only-binary=:all: --require-hashes -r "requirements/development-py${{ matrix.python-version }}.txt"
69
+ python -m pip install --no-deps --no-build-isolation -e ".[dev]"
70
+ python -m pip check
40
71
  # Wire subprocess coverage: the stdio e2e launches `python -m
41
72
  # relay_shell` in a child, so without this the server.py wrappers
42
73
  # register as uncovered. The .pth runs at every interpreter start;
@@ -50,9 +81,11 @@ jobs:
50
81
  # (2.7+; the distro package is older). Built from the pinned module version, whose
51
82
  # contents the Go checksum database verifies, on one matrix leg only.
52
83
  - name: Install Caddy (edge routing tests)
53
- if: matrix.python-version == '3.13'
84
+ if: matrix.python-version == '3.14'
54
85
  env:
55
86
  GOBIN: ${{ runner.temp }}/caddy-bin
87
+ # Avoid checksum-service HTTP/2 stream failures; TLS and Go sums stay enabled.
88
+ GODEBUG: http2client=0
56
89
  run: |
57
90
  command -v go
58
91
  go install github.com/caddyserver/caddy/v2/cmd/caddy@v2.11.7
@@ -80,8 +113,9 @@ jobs:
80
113
  # root-owned files cannot affect the steps before it). They still skip themselves if the
81
114
  # runner kernel does not support the notify channel; -rs prints that reason.
82
115
  - name: Test (seccomp-notify end to end, privileged)
83
- if: matrix.python-version == '3.13'
116
+ if: matrix.python-version == '3.14'
84
117
  env:
85
118
  PYTHONDONTWRITEBYTECODE: "1"
86
119
  run: |
87
- sudo -E "$(command -v python)" -m pytest -m seccomp -q -rs -p no:cacheprovider tests/test_seccomp.py
120
+ sudo --preserve-env=PYTHONDONTWRITEBYTECODE "$(command -v python)" -c 'from relay_shell.seccomp import platform_support; s=platform_support(); print(s); s.ok or exit("privileged seccomp coverage unavailable")'
121
+ sudo --preserve-env=PYTHONDONTWRITEBYTECODE "$(command -v python)" -m pytest -m seccomp -q -rs -p no:cacheprovider tests/test_seccomp.py
@@ -12,6 +12,15 @@ permissions:
12
12
  contents: read
13
13
  security-events: write
14
14
 
15
+
16
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
17
+ concurrency:
18
+ group: >-
19
+ codeql-${{ github.workflow }}-${{ github.event_name }}-${{
20
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
21
+ }}-${{ github.run_attempt }}
22
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
23
+
15
24
  jobs:
16
25
  analyze:
17
26
  name: Analyze (python)
@@ -29,15 +38,15 @@ jobs:
29
38
  persist-credentials: false
30
39
 
31
40
  - name: Initialize CodeQL
32
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
41
+ uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
33
42
  with:
34
43
  languages: ${{ matrix.language }}
35
44
 
36
45
  - name: Autobuild
37
- uses: github/codeql-action/autobuild@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
46
+ uses: github/codeql-action/autobuild@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
38
47
 
39
48
  - name: Perform CodeQL Analysis
40
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
49
+ uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
41
50
 
42
51
  # A14-D12: the workflows themselves are code that holds tokens. A separate job (not a
43
52
  # matrix entry) so the required check `Analyze (python) (python)` keeps its name. It is in
@@ -54,12 +63,12 @@ jobs:
54
63
  persist-credentials: false
55
64
 
56
65
  - name: Initialize CodeQL
57
- uses: github/codeql-action/init@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
66
+ uses: github/codeql-action/init@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
58
67
  with:
59
68
  languages: actions
60
69
  build-mode: none
61
70
 
62
71
  - name: Perform CodeQL Analysis
63
- uses: github/codeql-action/analyze@24c54180a607b1449ed407dd24f251e4e9147c8d # v4
72
+ uses: github/codeql-action/analyze@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4
64
73
  with:
65
74
  category: "/language:actions"
@@ -0,0 +1,42 @@
1
+ name: Copilot development setup
2
+ on:
3
+ workflow_dispatch:
4
+ pull_request:
5
+ paths:
6
+ - .github/workflows/copilot-setup-steps.yml
7
+ - requirements.txt
8
+ - requirements-build.txt
9
+ - "requirements/**"
10
+ - pyproject.toml
11
+ permissions:
12
+ contents: read
13
+
14
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
15
+ concurrency:
16
+ group: >-
17
+ copilot-setup-steps-${{ github.workflow }}-${{ github.event_name }}-${{
18
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
19
+ }}-${{ github.run_attempt }}
20
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
21
+
22
+ jobs:
23
+ copilot-setup-steps:
24
+ runs-on: ubuntu-26.04
25
+ timeout-minutes: 10
26
+ steps:
27
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
28
+ with:
29
+ persist-credentials: false
30
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
31
+ with:
32
+ python-version: "3.14"
33
+ - name: Prepare the constrained development environment
34
+ run: |
35
+ python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.14.txt
36
+ python -m pip install --no-deps --no-build-isolation -e ".[dev]"
37
+ python -m pip check
38
+ - name: Verify tools and representative test discovery
39
+ run: |
40
+ ruff --version
41
+ mypy --version
42
+ python -m pytest --collect-only -q tests/test_stdio_e2e.py tests/test_ssh_integration.py
@@ -6,6 +6,15 @@ on:
6
6
  permissions:
7
7
  contents: read
8
8
 
9
+
10
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
11
+ concurrency:
12
+ group: >-
13
+ dependency-review-${{ github.workflow }}-${{ github.event_name }}-${{
14
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
15
+ }}-${{ github.run_attempt }}
16
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
17
+
9
18
  jobs:
10
19
  dependency-review:
11
20
  runs-on: ubuntu-latest
@@ -13,6 +13,7 @@ on:
13
13
  - "mkdocs.yml"
14
14
  - "requirements-docs.txt"
15
15
  - ".github/workflows/docs.yml"
16
+ pull_request:
16
17
  workflow_dispatch:
17
18
 
18
19
  # Least-privilege default; the deploy job adds only the Pages-specific scopes.
@@ -21,13 +22,13 @@ permissions:
21
22
 
22
23
  # One Pages deployment at a time; let an in-flight publish finish.
23
24
  concurrency:
24
- group: pages
25
+ group: pages-${{ github.event.pull_request.number || github.ref }}
25
26
  cancel-in-progress: false
26
27
 
27
28
  jobs:
28
29
  build:
29
30
  name: Build (MkDocs Material)
30
- runs-on: ubuntu-latest
31
+ runs-on: ubuntu-24.04
31
32
  timeout-minutes: 10
32
33
  steps:
33
34
  - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
@@ -35,16 +36,17 @@ jobs:
35
36
  persist-credentials: false
36
37
  - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
37
38
  with:
38
- python-version: "3.14"
39
+ python-version: "3.12"
39
40
  cache: pip
40
41
  cache-dependency-path: requirements-docs.txt
41
42
  - name: Install MkDocs Material
42
43
  run: |
43
- python -m pip install --upgrade pip
44
- pip install -r requirements-docs.txt
44
+ python -m pip install -r requirements-docs.txt
45
+ python -m pip check
45
46
  - name: Build site (strict)
46
47
  run: mkdocs build --strict
47
48
  - name: Upload Pages artifact
49
+ if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
48
50
  uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5
49
51
  with:
50
52
  path: site
@@ -52,7 +54,8 @@ jobs:
52
54
  deploy:
53
55
  name: Deploy to GitHub Pages
54
56
  needs: build
55
- runs-on: ubuntu-latest
57
+ if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
58
+ runs-on: ubuntu-24.04
56
59
  timeout-minutes: 10
57
60
  permissions:
58
61
  pages: write # publish the Pages deployment
@@ -22,6 +22,15 @@ on:
22
22
  permissions:
23
23
  contents: read
24
24
 
25
+
26
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
27
+ concurrency:
28
+ group: >-
29
+ gitleaks-${{ github.workflow }}-${{ github.event_name }}-${{
30
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
31
+ }}-${{ github.run_attempt }}
32
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
33
+
25
34
  jobs:
26
35
  scan:
27
36
  name: gitleaks (secret scan)
@@ -30,11 +30,14 @@ jobs:
30
30
  with:
31
31
  python-version: "3.14"
32
32
  cache: "pip"
33
+ cache-dependency-path: |
34
+ requirements/development-py3.14.txt
33
35
 
34
36
  - name: Install
35
37
  run: |
36
- python -m pip install --upgrade pip
37
- pip install -c requirements.txt -e ".[dev]"
38
+ python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.14.txt
39
+ python -m pip install --no-deps --no-build-isolation -e ".[dev]"
40
+ python -m pip check
38
41
 
39
42
  - name: Run fuzz suite (HYPOTHESIS_PROFILE=ci)
40
43
  env:
@@ -19,6 +19,15 @@ on:
19
19
  permissions:
20
20
  contents: read
21
21
 
22
+
23
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
24
+ concurrency:
25
+ group: >-
26
+ pip-audit-${{ github.workflow }}-${{ github.event_name }}-${{
27
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
28
+ }}-${{ github.run_attempt }}
29
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
30
+
22
31
  jobs:
23
32
  audit:
24
33
  name: pip-audit (CVE scan)
@@ -33,14 +42,21 @@ jobs:
33
42
  with:
34
43
  python-version: "3.14"
35
44
  cache: "pip"
45
+ cache-dependency-path: |
46
+ requirements/development-py3.14.txt
47
+ requirements-audit.txt
36
48
 
37
- - name: Install package + dev extras
49
+ - name: Prepare separate locked subject and audit-tool environments
38
50
  run: |
39
- python -m pip install --upgrade pip pip-audit
40
- # Audit against the resolved dev set so transitive deps
41
- # introduced via the optional [dev] / [http] extras are also
42
- # covered.
43
- pip install -c requirements.txt -e ".[dev,http]"
51
+ python -m venv .audit-runtime
52
+ .audit-runtime/bin/python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.14.txt
53
+ .audit-runtime/bin/python -m pip install --no-deps --no-build-isolation -e ".[dev,http]"
54
+ .audit-runtime/bin/python -m pip check
55
+ python -m venv .audit-tool
56
+ .audit-tool/bin/python -m pip install --only-binary=:all: --require-hashes -r requirements-audit.txt
57
+ .audit-tool/bin/python -m pip check
58
+ audit_path=$(.audit-runtime/bin/python -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')
59
+ echo "AUDIT_SITE_PACKAGES=$audit_path" >> "$GITHUB_ENV"
44
60
 
45
61
  - name: Run pip-audit
46
62
  # `--skip-editable` excludes the local editable `relay-shell` install:
@@ -52,10 +68,14 @@ jobs:
52
68
  # audit and fails ("distribution marked as editable"). Every actual
53
69
  # third-party dependency (incl. the [dev]/[http] extras) is on PyPI and
54
70
  # still audited, and pip-audit still exits non-zero on any CVE found.
55
- run: pip-audit --skip-editable
71
+ run: .audit-tool/bin/pip-audit --path "$AUDIT_SITE_PACKAGES" --skip-editable
56
72
 
57
73
  - name: Audit the hash-locked release and SBOM tooling
58
74
  # requirements-build.txt / requirements-sbom.txt feed the jobs that build,
59
75
  # sign and publish (A14-D7); they are not part of the package's own tree, so
60
76
  # audit them explicitly. They are fully pinned, so no resolution is needed.
61
- run: pip-audit --disable-pip --no-deps -r requirements-build.txt -r requirements-sbom.txt
77
+ run: |
78
+ .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-build.txt
79
+ .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-sbom.txt
80
+ .audit-tool/bin/pip-audit --disable-pip --no-deps -r requirements-audit.txt
81
+
@@ -0,0 +1,115 @@
1
+ name: Release preparation rehearsal
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+
13
+ # Coalesce initial PR attempts; isolate reruns and retain non-PR evidence.
14
+ concurrency:
15
+ group: >-
16
+ release-rehearsal-${{ github.workflow }}-${{ github.event_name }}-${{
17
+ github.run_attempt == '1' && github.event.pull_request.number || github.run_id
18
+ }}-${{ github.run_attempt }}
19
+ cancel-in-progress: ${{ github.event_name == 'pull_request' && github.run_attempt == '1' }}
20
+
21
+ jobs:
22
+ rehearse:
23
+ name: package-and-install-rehearsal (${{ matrix.os }})
24
+ runs-on: ${{ matrix.os }}
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ os: [ubuntu-24.04, ubuntu-26.04]
29
+ timeout-minutes: 15
30
+ steps:
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
32
+ with:
33
+ persist-credentials: false
34
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
35
+ with:
36
+ python-version: "3.14"
37
+ - name: Prepare the locked rehearsal driver
38
+ run: |
39
+ python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.14.txt
40
+ - name: Rehearse package preparation and complete runtime restoration
41
+ run: python scripts/rehearse_release.py --output "$RUNNER_TEMP/release-rehearsal"
42
+ - name: Qualify actual installation and service-state recovery on the disposable VM
43
+ run: sudo --preserve-env=GITHUB_ACTIONS,RUNNER_ENVIRONMENT /usr/bin/python3 scripts/rehearse_install.py --output "$RUNNER_TEMP/install-rehearsal"
44
+ - name: Retain installer recovery receipt
45
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
46
+ with:
47
+ name: install-rehearsal-${{ matrix.os }}-${{ github.sha }}
48
+ path: ${{ runner.temp }}/install-rehearsal/receipt.json
49
+ if-no-files-found: error
50
+ retention-days: 30
51
+ - name: Retain source-bound rehearsal evidence
52
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
53
+ with:
54
+ name: release-rehearsal-${{ matrix.os }}-${{ github.sha }}
55
+ path: ${{ runner.temp }}/release-rehearsal/
56
+ if-no-files-found: error
57
+ retention-days: 30
58
+
59
+ cross-version:
60
+ name: cross-version-recovery
61
+ runs-on: ubuntu-26.04
62
+ timeout-minutes: 15
63
+ steps:
64
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
65
+ with:
66
+ persist-credentials: false
67
+ fetch-depth: 0
68
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
69
+ id: build-python
70
+ with:
71
+ python-version: "3.14"
72
+ - name: Prepare hash-locked build tools
73
+ run: |
74
+ python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.12.txt
75
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
76
+ with:
77
+ python-version: "3.14"
78
+ - name: Prepare independent audit tools with their locked interpreter
79
+ run: |
80
+ python -m venv .legacy-audit-tool
81
+ .legacy-audit-tool/bin/python -m pip install --only-binary=:all: --require-hashes -r requirements-audit.txt
82
+ - name: Audit the complete frozen baseline without executing it
83
+ run: |
84
+ set +e
85
+ .legacy-audit-tool/bin/pip-audit --requirement tests/fixtures/upgrade-v0.3/runtime.lock --require-hashes --disable-pip --strict --progress-spinner off --format json --output "$RUNNER_TEMP/legacy-audit.json"
86
+ audit_result=$?
87
+ set -e
88
+ test "$audit_result" -eq 0 || test "$audit_result" -eq 1
89
+ test -s "$RUNNER_TEMP/legacy-audit.json"
90
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7
91
+ id: legacy-python
92
+ with:
93
+ python-version: "3.12"
94
+ - name: Qualify admitted release upgrade and explicit rollback
95
+ run: sudo --preserve-env=GITHUB_ACTIONS,RUNNER_ENVIRONMENT /usr/bin/python3 scripts/rehearse_upgrade.py --build-python "${{ steps.build-python.outputs.python-path }}" --legacy-python "${{ steps.legacy-python.outputs.python-path }}" --audit-report "$RUNNER_TEMP/legacy-audit.json" --output "$RUNNER_TEMP/upgrade-rehearsal"
96
+ - name: Retain cross-version source-bound receipt
97
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7
98
+ with:
99
+ name: upgrade-rehearsal-${{ github.sha }}
100
+ path: ${{ runner.temp }}/upgrade-rehearsal/receipt.json
101
+ if-no-files-found: error
102
+ retention-days: 30
103
+
104
+ release-rehearsal:
105
+ name: release-rehearsal
106
+ if: ${{ always() }}
107
+ needs: [rehearse, cross-version]
108
+ runs-on: ubuntu-26.04
109
+ timeout-minutes: 2
110
+ steps:
111
+ - name: Require package, installer and cross-version recovery
112
+ env:
113
+ PACKAGE_RESULT: ${{ needs.rehearse.result }}
114
+ UPGRADE_RESULT: ${{ needs.cross-version.result }}
115
+ run: test "$PACKAGE_RESULT" = success && test "$UPGRADE_RESULT" = success
@@ -6,9 +6,9 @@ name: release
6
6
  # 1. verify - the tag is annotated, SSH-signed against the trusted key
7
7
  # in deploy/release-signing.pub, and matches the version
8
8
  # declared in pyproject.toml
9
- # 2. build - dev install (direct pins from requirements.txt as
10
- # constraints), full test suite on Python 3.14 (per-PR CI
11
- # exercises the 3.12-3.14 matrix), then `python -m build`
9
+ # 2. build - complete hashed development profile (historical tags retain
10
+ # direct constraints), full test suite on Python 3.14 (per-PR CI
11
+ # qualifies Ubuntu 24.04/3.12 and Ubuntu 26.04/3.14), then `python -m build`
12
12
  # to produce wheel + sdist. Build tooling comes from the
13
13
  # hash-locked requirements-build.txt and the build runs with
14
14
  # --no-isolation so no unpinned backend is fetched. This job
@@ -67,7 +67,7 @@ jobs:
67
67
  else
68
68
  tag="${REF#refs/tags/}"
69
69
  fi
70
- if ! printf '%s' "$tag" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+([+.\-].+)?$'; then
70
+ if ! [[ "$tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([+.-][0-9A-Za-z.-]+)?$ ]]; then
71
71
  echo "ERROR: $tag does not match the v<MAJOR>.<MINOR>.<PATCH> pattern" >&2
72
72
  exit 1
73
73
  fi
@@ -117,6 +117,7 @@ jobs:
117
117
  fi
118
118
  release_commit=$(git rev-list -n1 "${TAG}^{}")
119
119
  test -n "$release_commit"
120
+ test "$(git rev-parse HEAD)" = "$release_commit"
120
121
  # A tag is not an authority for its own signer trust anchor.
121
122
  # Fetch the protected default branch independently and require the
122
123
  # tagged commit to have passed the main-branch merge gate.
@@ -189,8 +190,15 @@ jobs:
189
190
  # --no-build-isolation installs and `python -m build` use these exact
190
191
  # (not freshly resolved) hatchling/build/twine.
191
192
  python -m pip install --require-hashes -r "$RUNNER_TEMP/requirements-build.txt"
192
- # Direct dependency pins from requirements.txt act as constraints.
193
- python -m pip install --no-build-isolation -c requirements.txt -e ".[dev]"
193
+ if [ -r requirements/development-py3.14.txt ]; then
194
+ # Refuse disagreement with the workflow's independently selected build tools.
195
+ python -m pip install --only-binary=:all: --require-hashes -r requirements/development-py3.14.txt -c "$RUNNER_TEMP/requirements-build.txt"
196
+ python -m pip install --no-deps --no-build-isolation -e ".[dev]"
197
+ else
198
+ # Historical signed tags predate complete profiles; retain their documented recovery path.
199
+ python -m pip install --no-build-isolation -c requirements.txt -e ".[dev]"
200
+ fi
201
+ python -m pip check
194
202
 
195
203
  - name: Run full test suite
196
204
  run: pytest -q
@@ -263,7 +271,7 @@ jobs:
263
271
  path: dist/
264
272
 
265
273
  - name: Publish via PyPI trusted publishing
266
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
274
+ uses: pypa/gh-action-pypi-publish@7245906fb61fa33a13554f9d5fb856fe61758086 # v1.14.3
267
275
  with:
268
276
  # Make a workflow re-run after a successful upload idempotent
269
277
  # instead of failing on "file already exists" - the runbook