ci-esta 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. ci_esta-0.1.0/.gitattributes +1 -0
  2. ci_esta-0.1.0/.github/actions/README.md +339 -0
  3. ci_esta-0.1.0/.github/actions/cancel-obsolete-pr-runs/action.yml +189 -0
  4. ci_esta-0.1.0/.github/actions/ci-observability/action.yml +174 -0
  5. ci_esta-0.1.0/.github/actions/ci-observability/ci-observability-v1.schema.json +1685 -0
  6. ci_esta-0.1.0/.github/actions/ci-observability-enrichment/action.yml +185 -0
  7. ci_esta-0.1.0/.github/actions/collect-ci-changes/action.yml +141 -0
  8. ci_esta-0.1.0/.github/actions/collect-pr-changes/action.yml +39 -0
  9. ci_esta-0.1.0/.github/actions/collect-pytest-changes/action.yml +25 -0
  10. ci_esta-0.1.0/.github/actions/coverage-policy/action.yml +29 -0
  11. ci_esta-0.1.0/.github/actions/current-pr-head/action.yml +38 -0
  12. ci_esta-0.1.0/.github/actions/learn-test-timings/action.yml +32 -0
  13. ci_esta-0.1.0/.github/actions/lint-ci-code/action.yml +173 -0
  14. ci_esta-0.1.0/.github/actions/lint-ci-code/filter_changed_diagnostics.py +78 -0
  15. ci_esta-0.1.0/.github/actions/lint-ci-code/validate_actions.py +70 -0
  16. ci_esta-0.1.0/.github/actions/native-windows-git/action.yml +10 -0
  17. ci_esta-0.1.0/.github/actions/native-windows-git/use_native_windows_git.ps1 +27 -0
  18. ci_esta-0.1.0/.github/actions/plan-pytest-shards/action.yml +40 -0
  19. ci_esta-0.1.0/.github/actions/plan-runner-pools/action.yml +34 -0
  20. ci_esta-0.1.0/.github/actions/publish-prometheus-snapshot/action.yml +54 -0
  21. ci_esta-0.1.0/.github/actions/publish-prometheus-snapshot/publish.py +153 -0
  22. ci_esta-0.1.0/.github/actions/pytest-canary-pr-state/action.yml +127 -0
  23. ci_esta-0.1.0/.github/actions/pytest-expansion/action.yml +29 -0
  24. ci_esta-0.1.0/.github/actions/pytest-failure-canary/README.md +13 -0
  25. ci_esta-0.1.0/.github/actions/pytest-failure-canary/action.yml +104 -0
  26. ci_esta-0.1.0/.github/actions/pytest-timings/action.yml +64 -0
  27. ci_esta-0.1.0/.github/actions/resolve-incremental-pr-base/action.yml +84 -0
  28. ci_esta-0.1.0/.github/actions/resolve-incremental-pr-base/resolve.cjs +475 -0
  29. ci_esta-0.1.0/.github/actions/resolve-lane-evidence/action.yml +85 -0
  30. ci_esta-0.1.0/.github/actions/resolve-lane-evidence/inputs.cjs +163 -0
  31. ci_esta-0.1.0/.github/actions/resolve-pytest-evidence/action.yml +61 -0
  32. ci_esta-0.1.0/.github/actions/resolve-security-trigger/action.yml +393 -0
  33. ci_esta-0.1.0/.github/actions/resolve-security-trigger/inventory.cjs +28 -0
  34. ci_esta-0.1.0/.github/actions/resolve-security-trigger/scope.cjs +40 -0
  35. ci_esta-0.1.0/.github/actions/reuse-ecr-image/action.yml +38 -0
  36. ci_esta-0.1.0/.github/actions/reuse-ecr-image/reuse_ecr_image.sh +157 -0
  37. ci_esta-0.1.0/.github/actions/semgrep-scan/action.yml +163 -0
  38. ci_esta-0.1.0/.github/actions/semgrep-scan/setup.sh +15 -0
  39. ci_esta-0.1.0/.github/actions/semgrep-scan-ci-code/action.yml +45 -0
  40. ci_esta-0.1.0/.github/actions/setup-cached-uv/action.yml +60 -0
  41. ci_esta-0.1.0/.github/actions/setup-python-uv/action.yml +24 -0
  42. ci_esta-0.1.0/.github/actions/test-impact/action.yml +105 -0
  43. ci_esta-0.1.0/.github/actions/trivy-scan-ci-code/action.yml +45 -0
  44. ci_esta-0.1.0/.github/actions/trivy-scan-image/action.yml +84 -0
  45. ci_esta-0.1.0/.github/actions/typecheck-scope/action.yml +80 -0
  46. ci_esta-0.1.0/.github/actions/typecheck-scope/proof.cjs +19 -0
  47. ci_esta-0.1.0/.github/actions/typecheck-scope/scope.sh +44 -0
  48. ci_esta-0.1.0/.github/actions/validation-gate/action.yml +37 -0
  49. ci_esta-0.1.0/.github/actions/validation-gate/gate.cjs +33 -0
  50. ci_esta-0.1.0/.github/actions/verify-pytest-completion/action.yml +20 -0
  51. ci_esta-0.1.0/.github/test-lanes.json +36 -0
  52. ci_esta-0.1.0/.github/workflows/ci.yml +145 -0
  53. ci_esta-0.1.0/.github/workflows/observe-ci.yml +24 -0
  54. ci_esta-0.1.0/.github/workflows/release.yml +67 -0
  55. ci_esta-0.1.0/.github/workflows/report.yml +95 -0
  56. ci_esta-0.1.0/.gitignore +12 -0
  57. ci_esta-0.1.0/CHANGELOG.md +11 -0
  58. ci_esta-0.1.0/CONTRIBUTING.md +16 -0
  59. ci_esta-0.1.0/LICENSE +202 -0
  60. ci_esta-0.1.0/PKG-INFO +251 -0
  61. ci_esta-0.1.0/PROVENANCE.md +64 -0
  62. ci_esta-0.1.0/README.md +223 -0
  63. ci_esta-0.1.0/RELEASING.md +23 -0
  64. ci_esta-0.1.0/docs/adoption-audit.md +144 -0
  65. ci_esta-0.1.0/docs/agents.md +67 -0
  66. ci_esta-0.1.0/docs/alternatives.md +15 -0
  67. ci_esta-0.1.0/docs/assets/ci-esta-badge-logo.svg +1 -0
  68. ci_esta-0.1.0/docs/assets/ci-esta-mark.svg +15 -0
  69. ci_esta-0.1.0/docs/assets/social-preview.png +0 -0
  70. ci_esta-0.1.0/docs/assets/social-preview.svg +28 -0
  71. ci_esta-0.1.0/docs/assets/sticky-banner-dark.svg +34 -0
  72. ci_esta-0.1.0/docs/assets/sticky-banner-light.svg +34 -0
  73. ci_esta-0.1.0/docs/benchmarks/2026-10-22-selective-report.json +567 -0
  74. ci_esta-0.1.0/docs/benchmarks/2026-10-22-selective-report.md +56 -0
  75. ci_esta-0.1.0/docs/benchmarks/2026-10-22.md +71 -0
  76. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/due-39867723644.json +2786 -0
  77. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/due-39868243678.json +2786 -0
  78. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/due-test-inventory.json +1 -0
  79. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/obligation-39866009916.json +6206 -0
  80. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/obligation-39869242309.json +6226 -0
  81. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/obligation-test-inventory.json +1 -0
  82. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/selective-39867390350.json +2901 -0
  83. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution/selective-39871515005.json +2901 -0
  84. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-execution.md +100 -0
  85. ci_esta-0.1.0/docs/benchmarks/2026-10-23-critical-path.md +114 -0
  86. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-ci-esta-39863488268.json +38 -0
  87. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-ci-esta-39864390246.json +38 -0
  88. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-due-39864032259.json +47 -0
  89. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-due-39864404935.json +47 -0
  90. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-pytest-obligation-39863872668.json +128 -0
  91. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-pytest-obligation-39864399959.json +128 -0
  92. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-pytest-selective-39863865180.json +92 -0
  93. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/cache-pytest-selective-39864395427.json +92 -0
  94. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/ci-esta-39862792267.json +885 -0
  95. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/ci-esta-39863205566.json +1251 -0
  96. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/due-39862718626.json +2606 -0
  97. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/observer-ci-esta.json +10 -0
  98. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/observer-due.json +10 -0
  99. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/observer-pytest-obligation.json +10 -0
  100. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/observer-pytest-selective.json +10 -0
  101. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/pytest-obligation-39862718305.json +6126 -0
  102. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit/pytest-selective-39862718435.json +2661 -0
  103. ci_esta-0.1.0/docs/benchmarks/2026-10-23-toolkit.md +140 -0
  104. ci_esta-0.1.0/docs/ci-optimization.md +139 -0
  105. ci_esta-0.1.0/docs/critical-path-optimizations.md +51 -0
  106. ci_esta-0.1.0/docs/critical-path.md +77 -0
  107. ci_esta-0.1.0/docs/diagnostics-runner-test-plan.md +14 -0
  108. ci_esta-0.1.0/docs/environment-cache.md +45 -0
  109. ci_esta-0.1.0/docs/evidence-reuse.md +69 -0
  110. ci_esta-0.1.0/docs/github-actions-observability.md +158 -0
  111. ci_esta-0.1.0/docs/integrations.md +69 -0
  112. ci_esta-0.1.0/docs/learned-test-timings.md +83 -0
  113. ci_esta-0.1.0/docs/migration-inventory.json +533 -0
  114. ci_esta-0.1.0/docs/migration-test-plan.md +38 -0
  115. ci_esta-0.1.0/docs/python-ci-tuning.md +48 -0
  116. ci_esta-0.1.0/docs/runners-and-diagnostics.md +115 -0
  117. ci_esta-0.1.0/docs/selected-gates.md +47 -0
  118. ci_esta-0.1.0/docs/upstream-closure.md +28 -0
  119. ci_esta-0.1.0/docs/upstreaming.md +23 -0
  120. ci_esta-0.1.0/docs/validation.md +34 -0
  121. ci_esta-0.1.0/examples/gigaverse-ci-drill.yaml +63 -0
  122. ci_esta-0.1.0/importlinter.toml +15 -0
  123. ci_esta-0.1.0/pyproject.toml +60 -0
  124. ci_esta-0.1.0/scripts/.gitkeep +0 -0
  125. ci_esta-0.1.0/scripts/__init__.py +0 -0
  126. ci_esta-0.1.0/scripts/ci/__init__.py +0 -0
  127. ci_esta-0.1.0/scripts/ci/coverage-policy.cjs +30 -0
  128. ci_esta-0.1.0/scripts/ci/environment_cache.py +78 -0
  129. ci_esta-0.1.0/scripts/ci/github_script_surface.py +10 -0
  130. ci_esta-0.1.0/scripts/ci/pytest_selective_requirement.py +13 -0
  131. ci_esta-0.1.0/scripts/render_sticky_banners.py +81 -0
  132. ci_esta-0.1.0/src/ci_esta/__init__.py +2 -0
  133. ci_esta-0.1.0/src/ci_esta/ci_observability/__init__.py +16 -0
  134. ci_esta-0.1.0/src/ci_esta/ci_observability/action.py +422 -0
  135. ci_esta-0.1.0/src/ci_esta/ci_observability/brand.py +68 -0
  136. ci_esta-0.1.0/src/ci_esta/ci_observability/critical_path.py +115 -0
  137. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/__init__.py +28 -0
  138. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/__main__.py +8 -0
  139. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/ai.py +235 -0
  140. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/backend/__init__.py +7 -0
  141. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/backend/artifacts.py +179 -0
  142. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/backend/github.py +173 -0
  143. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/backend/legacy.py +149 -0
  144. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/backend/models.py +109 -0
  145. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/cli.py +197 -0
  146. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/models.py +13 -0
  147. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/primitives.py +51 -0
  148. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/profiles.py +29 -0
  149. ci_esta-0.1.0/src/ci_esta/ci_observability/enrichment/prometheus.py +55 -0
  150. ci_esta-0.1.0/src/ci_esta/ci_observability/github.py +305 -0
  151. ci_esta-0.1.0/src/ci_esta/ci_observability/lanes.py +187 -0
  152. ci_esta-0.1.0/src/ci_esta/ci_observability/markdown.py +46 -0
  153. ci_esta-0.1.0/src/ci_esta/ci_observability/models.py +429 -0
  154. ci_esta-0.1.0/src/ci_esta/ci_observability/report.py +847 -0
  155. ci_esta-0.1.0/src/ci_esta/ci_observability/ruff.toml +5 -0
  156. ci_esta-0.1.0/src/ci_esta/ci_observability/selection.py +501 -0
  157. ci_esta-0.1.0/src/ci_esta/ci_observability/timeline.py +223 -0
  158. ci_esta-0.1.0/src/ci_esta/ci_observability/workflow_graph.py +31 -0
  159. ci_esta-0.1.0/src/ci_esta/cli.py +65 -0
  160. ci_esta-0.1.0/src/ci_esta/diagnostics/__init__.py +17 -0
  161. ci_esta-0.1.0/src/ci_esta/diagnostics/__main__.py +3 -0
  162. ci_esta-0.1.0/src/ci_esta/diagnostics/resources.sh +86 -0
  163. ci_esta-0.1.0/src/ci_esta/drill.py +1401 -0
  164. ci_esta-0.1.0/src/ci_esta/github_script_surface.py +65 -0
  165. ci_esta-0.1.0/src/ci_esta/image_evidence/__init__.py +93 -0
  166. ci_esta-0.1.0/src/ci_esta/image_evidence/cli.py +74 -0
  167. ci_esta-0.1.0/src/ci_esta/image_evidence/nodes.py +302 -0
  168. ci_esta-0.1.0/src/ci_esta/image_evidence/observer.py +38 -0
  169. ci_esta-0.1.0/src/ci_esta/image_evidence/plan.py +91 -0
  170. ci_esta-0.1.0/src/ci_esta/image_evidence/pulls.sh +78 -0
  171. ci_esta-0.1.0/src/ci_esta/image_evidence/runtime.py +66 -0
  172. ci_esta-0.1.0/src/ci_esta/py.typed +0 -0
  173. ci_esta-0.1.0/src/ci_esta/resources.py +17 -0
  174. ci_esta-0.1.0/src/ci_esta/testing/__init__.py +3 -0
  175. ci_esta-0.1.0/src/ci_esta/testing/action.py +139 -0
  176. ci_esta-0.1.0/src/ci_esta/testing/artifacts.py +24 -0
  177. ci_esta-0.1.0/src/ci_esta/testing/canary_action.py +123 -0
  178. ci_esta-0.1.0/src/ci_esta/testing/canary_run.py +290 -0
  179. ci_esta-0.1.0/src/ci_esta/testing/cli.py +220 -0
  180. ci_esta-0.1.0/src/ci_esta/testing/coverage.py +50 -0
  181. ci_esta-0.1.0/src/ci_esta/testing/evidence.py +27 -0
  182. ci_esta-0.1.0/src/ci_esta/testing/evidence_scripts/collect.sh +61 -0
  183. ci_esta-0.1.0/src/ci_esta/testing/evidence_scripts/verify.sh +25 -0
  184. ci_esta-0.1.0/src/ci_esta/testing/gha_expressions.py +222 -0
  185. ci_esta-0.1.0/src/ci_esta/testing/hygiene.py +22 -0
  186. ci_esta-0.1.0/src/ci_esta/testing/import_boundaries.py +491 -0
  187. ci_esta-0.1.0/src/ci_esta/testing/import_boundary_config.py +143 -0
  188. ci_esta-0.1.0/src/ci_esta/testing/junit.py +333 -0
  189. ci_esta-0.1.0/src/ci_esta/testing/local_shards.py +96 -0
  190. ci_esta-0.1.0/src/ci_esta/testing/postgres.py +46 -0
  191. ci_esta-0.1.0/src/ci_esta/testing/pytest_completion.py +51 -0
  192. ci_esta-0.1.0/src/ci_esta/testing/pytest_expansion.py +48 -0
  193. ci_esta-0.1.0/src/ci_esta/testing/pytest_watchdog.py +120 -0
  194. ci_esta-0.1.0/src/ci_esta/testing/runner_pools.py +43 -0
  195. ci_esta-0.1.0/src/ci_esta/testing/timing_feedback.py +28 -0
  196. ci_esta-0.1.0/src/ci_esta/testing/workflow_adoption.py +55 -0
  197. ci_esta-0.1.0/src/ci_esta/testing/workflow_policy.py +370 -0
  198. ci_esta-0.1.0/tests/__init__.py +0 -0
  199. ci_esta-0.1.0/tests/ci/ci_drill_probe_4957.json +167 -0
  200. ci_esta-0.1.0/tests/ci/github_rest_fake.cjs +55 -0
  201. ci_esta-0.1.0/tests/ci/github_script_rest_surface.json +1108 -0
  202. ci_esta-0.1.0/tests/ci/image_evidence/test_nodes.py +226 -0
  203. ci_esta-0.1.0/tests/ci/image_evidence/test_pull_driver.py +87 -0
  204. ci_esta-0.1.0/tests/ci/image_evidence/test_pull_plan.py +84 -0
  205. ci_esta-0.1.0/tests/ci/image_evidence/test_runtime.py +108 -0
  206. ci_esta-0.1.0/tests/ci/test_ci_drill.py +646 -0
  207. ci_esta-0.1.0/tests/ci/test_coverage_policy.py +57 -0
  208. ci_esta-0.1.0/tests/ci/test_drill_slots.py +634 -0
  209. ci_esta-0.1.0/tests/ci/test_environment_cache.py +59 -0
  210. ci_esta-0.1.0/tests/ci/test_execution_context.py +92 -0
  211. ci_esta-0.1.0/tests/ci/test_github_rest_surface.py +159 -0
  212. ci_esta-0.1.0/tests/ci/test_image_evidence.py +126 -0
  213. ci_esta-0.1.0/tests/ci/test_incremental_evidence.py +1326 -0
  214. ci_esta-0.1.0/tests/ci/test_inventory_evidence.py +90 -0
  215. ci_esta-0.1.0/tests/ci/test_lane_receipt.py +363 -0
  216. ci_esta-0.1.0/tests/ci/test_portable_receipts.py +67 -0
  217. ci_esta-0.1.0/tests/ci/test_pr_change_evidence.py +57 -0
  218. ci_esta-0.1.0/tests/ci/test_repository_routing.py +30 -0
  219. ci_esta-0.1.0/tests/ci/test_security_change_scope.py +95 -0
  220. ci_esta-0.1.0/tests/ci/test_semgrep_runner.py +187 -0
  221. ci_esta-0.1.0/tests/ci/test_typecheck_scope_action.py +148 -0
  222. ci_esta-0.1.0/tests/ci/test_validation_gate.py +61 -0
  223. ci_esta-0.1.0/tests/ci_observability/__init__.py +3 -0
  224. ci_esta-0.1.0/tests/ci_observability/builders.py +79 -0
  225. ci_esta-0.1.0/tests/ci_observability/test_action.py +723 -0
  226. ci_esta-0.1.0/tests/ci_observability/test_action_metadata.py +142 -0
  227. ci_esta-0.1.0/tests/ci_observability/test_brand.py +20 -0
  228. ci_esta-0.1.0/tests/ci_observability/test_critical_path.py +83 -0
  229. ci_esta-0.1.0/tests/ci_observability/test_enrichment_ai.py +147 -0
  230. ci_esta-0.1.0/tests/ci_observability/test_enrichment_backend.py +229 -0
  231. ci_esta-0.1.0/tests/ci_observability/test_enrichment_cli.py +219 -0
  232. ci_esta-0.1.0/tests/ci_observability/test_enrichment_profiles.py +40 -0
  233. ci_esta-0.1.0/tests/ci_observability/test_generic_profile.py +11 -0
  234. ci_esta-0.1.0/tests/ci_observability/test_github.py +435 -0
  235. ci_esta-0.1.0/tests/ci_observability/test_lanes.py +261 -0
  236. ci_esta-0.1.0/tests/ci_observability/test_report.py +827 -0
  237. ci_esta-0.1.0/tests/ci_observability/test_selection.py +543 -0
  238. ci_esta-0.1.0/tests/ci_observability/test_step_timings.py +123 -0
  239. ci_esta-0.1.0/tests/ci_observability/test_timeline.py +139 -0
  240. ci_esta-0.1.0/tests/ci_observability/test_workflow_graph.py +19 -0
  241. ci_esta-0.1.0/tests/github_actions/test_cancel_obsolete_pr_runs.py +66 -0
  242. ci_esta-0.1.0/tests/github_actions/test_immutable_actions.py +22 -0
  243. ci_esta-0.1.0/tests/github_actions/test_publish_prometheus_snapshot_action.py +331 -0
  244. ci_esta-0.1.0/tests/github_actions/test_reuse_ecr_image_action.py +219 -0
  245. ci_esta-0.1.0/tests/github_actions/test_selective_delegation.py +24 -0
  246. ci_esta-0.1.0/tests/github_actions/test_trivy_scan_image_action.py +49 -0
  247. ci_esta-0.1.0/tests/github_actions/test_workflow_primitives.py +53 -0
  248. ci_esta-0.1.0/tests/test_cli.py +30 -0
  249. ci_esta-0.1.0/tests/testing/__init__.py +0 -0
  250. ci_esta-0.1.0/tests/testing/git_repo.py +36 -0
  251. ci_esta-0.1.0/tests/testing/test_architecture.py +26 -0
  252. ci_esta-0.1.0/tests/testing/test_canary_run.py +382 -0
  253. ci_esta-0.1.0/tests/testing/test_canary_toolchain.py +31 -0
  254. ci_esta-0.1.0/tests/testing/test_cli.py +152 -0
  255. ci_esta-0.1.0/tests/testing/test_coverage.py +38 -0
  256. ci_esta-0.1.0/tests/testing/test_diagnostics.py +142 -0
  257. ci_esta-0.1.0/tests/testing/test_gha_expressions.py +147 -0
  258. ci_esta-0.1.0/tests/testing/test_import_boundaries.py +367 -0
  259. ci_esta-0.1.0/tests/testing/test_import_boundary_config.py +103 -0
  260. ci_esta-0.1.0/tests/testing/test_junit.py +261 -0
  261. ci_esta-0.1.0/tests/testing/test_learn_timings.py +94 -0
  262. ci_esta-0.1.0/tests/testing/test_local_shards.py +180 -0
  263. ci_esta-0.1.0/tests/testing/test_postgres.py +48 -0
  264. ci_esta-0.1.0/tests/testing/test_pytest_canary_action.py +226 -0
  265. ci_esta-0.1.0/tests/testing/test_pytest_canary_pr_state_action.py +28 -0
  266. ci_esta-0.1.0/tests/testing/test_pytest_completion.py +109 -0
  267. ci_esta-0.1.0/tests/testing/test_pytest_evidence.py +301 -0
  268. ci_esta-0.1.0/tests/testing/test_pytest_timings_action.py +81 -0
  269. ci_esta-0.1.0/tests/testing/test_runner_pools.py +39 -0
  270. ci_esta-0.1.0/tests/testing/test_selective_ownership.py +10 -0
  271. ci_esta-0.1.0/tests/testing/test_workflow_adoption.py +30 -0
  272. ci_esta-0.1.0/tests/testing/test_workflow_policy.py +485 -0
  273. ci_esta-0.1.0/uv.lock +763 -0
@@ -0,0 +1 @@
1
+ *.sh text eol=lf
@@ -0,0 +1,339 @@
1
+ # Shared CI building blocks
2
+
3
+ Reusable pieces so `gigaverse-ai` and `gigaverse-backend` share identical CI
4
+ mechanics without forcing repository-specific build and test layouts into one
5
+ workflow.
6
+
7
+ Use `gigaverse-app/ci-esta/.github/actions/<name>@<reviewed-commit>` for the
8
+ extracted actions. The backend-common reusable workflows listed below retain
9
+ Gigaverse event/security/release policy; they are external compositions, not
10
+ workflows shipped by CI-esta. Start with the [portable adoption guide](../../docs/ci-optimization.md)
11
+ and check the [revision-bound adoption audit](../../docs/adoption-audit.md).
12
+
13
+ Each piece is matched to the shape of the shared work:
14
+
15
+ | Piece | Kind | Reused by |
16
+ |-------|------|-----------|
17
+ | `collect-ci-changes` | composite action | workflow/action/shell change classification |
18
+ | `current-pr-head` | composite action | live stale-rerun admission |
19
+ | `resolve-security-trigger` | composite action | every security workflow, both repos |
20
+ | `resolve-incremental-pr-base` | composite action | automatic security and CI-code PR validation |
21
+ | `trivy-scan-image` | composite action | each repo's Trivy workflow |
22
+ | `backend-common/reusable-semgrep.yaml` | reusable workflow | each repo's Semgrep stub |
23
+ | `lint-ci-code` | composite action | `backend-common/reusable-ci-code-validation.yaml` |
24
+ | `semgrep-scan-ci-code` | composite action | `backend-common/reusable-ci-code-validation.yaml` |
25
+ | `trivy-scan-ci-code` | composite action | `backend-common/reusable-ci-code-validation.yaml` |
26
+ | `backend-common/reusable-ci-code-validation.yaml` | reusable workflow | path-filtered CI validation callers |
27
+ | `backend-common/reusable-current-pr-head.yaml` | reusable workflow | stale manual-rerun admission in PR validation |
28
+ | `cancel-obsolete-pr-runs` | composite action | post-admission stale-run cancellation from existing jobs |
29
+ | `setup-python-uv` | composite action | backend-common test and type-check workflows |
30
+ | `setup-cached-uv` | composite action | portable exact environment restore, repair and locked synchronization |
31
+ | `publish-prometheus-snapshot` | composite action | bounded CI/build metric producers |
32
+ | `ci-observability` | composite action | CI latency/cost sticky and evidence in all three repos |
33
+ | `reuse-ecr-image` | composite action | deployments that can reuse an exact verified image |
34
+ | `pytest-failure-canary` | composite action | bounded whole-file retries from prior JUnit failures |
35
+ | `pytest-canary-pr-state` | composite action | stale-run-safe pytest-red PR label transitions |
36
+ | `plan-pytest-shards` | composite action | exhaustive file partitioning with the shared duration planner |
37
+ | `resolve-lane-evidence` | composite action | passing-result reuse for whole trees or declared complete input paths |
38
+
39
+ For stale-rerun safety, first PR attempts use a PR-number concurrency group and
40
+ reruns use their unique run ID, so a rerun can never replace or cancel current
41
+ work. Call `backend-common/reusable-current-pr-head.yaml` before a rerun's expensive jobs. A
42
+ first-attempt job also calls `cancel-obsolete-pr-runs`: this cancels a rerun
43
+ that passed admission and then became stale, without adding a runner job. It
44
+ resolves the current commit's first parent through check runs, avoiding
45
+ GitHub's eventually-consistent workflow-run listing after a normal one-commit
46
+ agent push; the workflow index remains the fallback for other histories.
47
+
48
+ ## `ci-observability`
49
+
50
+ Discovers jobs, commit statuses, and all rerun attempts through GitHub's
51
+ paginated API; combines them with explicit required-gate/direct-prerequisite
52
+ metadata and optional typed repository signals; and emits a versioned JSON
53
+ artifact, sticky/summary Markdown, and bounded Prometheus exposition. Workflow
54
+ job names, matrix size, and step ordering are never accounting keys.
55
+
56
+ ```yaml
57
+ - uses: gigaverse-app/ci-esta/.github/actions/ci-observability@v0.1
58
+ with:
59
+ github-token: ${{ github.token }}
60
+ commit-sha: ${{ github.event.pull_request.head.sha }}
61
+ pull-request-number: ${{ github.event.pull_request.number }}
62
+ required-gates-json: >-
63
+ [{"kind":"commit_status","context":"required-check"}]
64
+ ```
65
+
66
+ Run it on an existing off-path host. See
67
+ [`docs/github-actions-observability.md`](../../docs/github-actions-observability.md) for the versioned
68
+ data, timing, cost, graph, metric, and failure contracts. When the run's
69
+ `test-impact` action uploaded a selection explanation, the sticky opens with a
70
+ test-selection section; consumers pass nothing extra for it.
71
+
72
+ ## `reuse-ecr-image`
73
+
74
+ Promotes an immutable, previously verified image to the tag consumed by a
75
+ deployment by copying its ECR manifest inside the same repository. It never
76
+ pulls or rebuilds layers. The action emits `reused=false` on a missing image,
77
+ registry error, repository mismatch, target conflict, promotion failure, or
78
+ digest mismatch so the caller can continue through its ordinary build path.
79
+
80
+ The caller owns the artifact identity and verification policy. This action only
81
+ proves that the source and target ECR references resolve to the same digest.
82
+
83
+ ```yaml
84
+ - id: image-reuse
85
+ uses: gigaverse-app/ci-esta/.github/actions/reuse-ecr-image@v0.1
86
+ with:
87
+ source-image: ${{ env.REPOSITORY }}:verified-${{ steps.identity.outputs.key }}
88
+ target-image: ${{ env.REPOSITORY }}:${{ github.sha }}
89
+
90
+ - if: steps.image-reuse.outputs.reused != 'true'
91
+ run: ./build-and-push-image.sh
92
+ ```
93
+
94
+ ## `publish-prometheus-snapshot`
95
+
96
+ Replaces one stable Pushgateway group from a Prometheus text exposition file.
97
+ The action always adds `GITHUB_REPOSITORY` to the grouping key, uses `PUT`, and
98
+ handles `/` in grouping values with Pushgateway's required URL-safe base64
99
+ encoding.
100
+
101
+ ```yaml
102
+ - uses: gigaverse-app/ci-esta/.github/actions/publish-prometheus-snapshot@v0.1
103
+ with:
104
+ metrics-file: build-metrics.prom
105
+ pushgateway-url: ${{ secrets.PUSHGW_URL }}
106
+ job: docker_build
107
+ grouping-labels: >-
108
+ {"env":"dev","service":"edge_service"}
109
+ username: ghci
110
+ password: ${{ secrets.PUSHGW_PASS }}
111
+ ```
112
+
113
+ Grouping labels identify retained state, not individual events:
114
+
115
+ - Use bounded dimensions such as environment, service, or a persistent branch.
116
+ - Never use run IDs or commit SHAs; the action rejects those dimensions.
117
+ - For unbounded feature branches, publish only artifacts or add an explicit
118
+ lifecycle cleanup workflow.
119
+ - Use `failure-mode: fail` only when publishing telemetry must gate the caller.
120
+ The default `warn` mode reports an annotation without failing delivery work.
121
+
122
+ ## `resolve-security-trigger`
123
+
124
+ Decides whether a security scan should run, from the same triggers the
125
+ non-default test framework uses (`docs/testing/expensive-test-policy.md` in the
126
+ consumers): PR `risk:security` label, a write+ collaborator
127
+ `/gv-test security` | `@gv-ci run security` comment, push, or manual dispatch.
128
+ Emits `should_run`, `ref`, `pr_number`, `mode`, and `same_repo`. For a shared
129
+ resolver, leave `require-same-repo` false and gate only secret-bearing consumers
130
+ on `same_repo`; source-only scans can still run on forks.
131
+
132
+ Consumers that produce reusable pre-merge artifacts can set
133
+ `automatic-same-repo-pull-requests: "true"`. That admits opened, reopened,
134
+ synchronized, and ready-for-review non-draft, non-bot PRs only when their head
135
+ branch belongs to the consumer repository. The default remains the opt-in
136
+ label/comment policy. Automatic PR scans use the newest successful ancestor
137
+ from this exact workflow and PR as their diff base, so later pushes scan only
138
+ work that has not already passed. The stable job name or reusable-workflow
139
+ suffix named by `automatic-pr-evidence-job-name` must also have succeeded; a
140
+ caller prefix such as `validate / ` does not change the evidence identity. A
141
+ green workflow whose scan jobs were skipped is not reusable evidence. A
142
+ cancelled workflow is reusable only when every job pattern declared by
143
+ `automatic-pr-cancelled-evidence-job-patterns` succeeded. Evidence expires
144
+ after seven days. A missing or failed run, rewritten branch, API failure, or
145
+ any other uncertainty falls back to the PR base and scans the whole PR diff.
146
+ Automatic callers grant the resolver job `actions: read`, `checks: read`, and
147
+ `contents: read`; cancellation in the same job may require `actions: write`
148
+ instead.
149
+
150
+ `dependency-paths` defines the package manifests, lockfiles, Dockerfiles, and
151
+ other inputs that determine an inventory scan. For automatic PRs and pushes,
152
+ `deps_changed=false` is emitted only after the compare API proves none of those
153
+ paths changed. Explicit label/comment/manual scans and scheduled scans always
154
+ emit `true`; comparison uncertainty also fails open to `true`.
155
+
156
+ For automatic PRs, `semgrep_changed=false` is emitted before scanner job
157
+ allocation only when the complete incremental diff contains no added, modified,
158
+ or renamed file outside `automatic-pr-ignored-paths`. The defaults contain the
159
+ two machine-owned bump provenance files; explicit and uncertain runs stay
160
+ fail-open.
161
+
162
+ ```yaml
163
+ - id: gate
164
+ uses: gigaverse-app/ci-esta/.github/actions/resolve-security-trigger@v0.1
165
+ with:
166
+ require-same-repo: "true" # set for image-building scans (Trivy); needs same-repo secrets
167
+ automatic-same-repo-pull-requests: "true" # optional; default false
168
+ dependency-paths: |
169
+ pyproject.toml
170
+ uv.lock
171
+ deployment/Dockerfile
172
+ ```
173
+
174
+ ## `trivy-scan-image`
175
+
176
+ Runs `aquasecurity/trivy-action` against an already-materialized image or
177
+ filesystem package inventory with the shared severity policy (`CRITICAL,HIGH`,
178
+ `ignore-unfixed`, fail on findings). Inventory
179
+ creation is the consumer's job: use `image-ref` for an OS image, or let the
180
+ native package manager materialize an environment and use `scan-type: fs` plus
181
+ `scan-ref`. This is important for lock formats with environment markers: Trivy
182
+ matches installed package versions to advisories; it does not replace the
183
+ package manager's resolution semantics.
184
+
185
+ The action pins Trivy `v0.72.0` by default; consumers can use `trivy-version`
186
+ for a controlled compatibility override. `vuln-type` defaults to both OS and
187
+ language libraries, while `scanners` defaults to `vuln`: the shared Semgrep
188
+ gate owns source-code vulnerability and secret scanning once at repository
189
+ scope. A consumer without that gate must explicitly request the additional
190
+ scanner. Callers should similarly narrow `vuln-type` when they deliberately
191
+ split package inventories.
192
+
193
+ ```yaml
194
+ - uses: gigaverse-app/ci-esta/.github/actions/trivy-scan-image@v0.1
195
+ with:
196
+ image-ref: "trivy-scan:my_service"
197
+ ```
198
+
199
+ ```yaml
200
+ - run: uv sync --frozen --all-groups --no-install-project
201
+ - uses: gigaverse-app/ci-esta/.github/actions/trivy-scan-image@v0.1
202
+ with:
203
+ # rootfs inventories installed .dist-info packages; fs is for manifests.
204
+ scan-type: rootfs
205
+ scan-ref: .venv
206
+ vuln-type: library
207
+ scanners: vuln
208
+ ```
209
+
210
+ Use `skip-dirs` only for a documented inventory boundary (for example, a
211
+ package manager's bundled implementation dependencies that the product never
212
+ executes), not to suppress an actionable vulnerable application package.
213
+
214
+ When two inventories run sequentially in one job, the first invocation should
215
+ use the defaults. On the second, set `skip-setup-trivy: "true"` and
216
+ `cache: "false"`: Trivy is already on `$PATH`, and bypassing a second cache
217
+ restore preserves the freshly downloaded database in the shared cache directory
218
+ instead of restoring stale data over it.
219
+
220
+ Poetry follows the same contract: Poetry evaluates its lock, groups, Python
221
+ version, and platform markers; Trivy evaluates the resulting installed package
222
+ metadata. Keep the virtual environment in-project so the scan target is explicit
223
+ and does not depend on Poetry's global cache layout.
224
+
225
+ ```yaml
226
+ - run: poetry install --all-groups --no-root
227
+ env:
228
+ POETRY_VIRTUALENVS_IN_PROJECT: "true"
229
+ - uses: gigaverse-app/ci-esta/.github/actions/trivy-scan-image@v0.1
230
+ with:
231
+ scan-type: rootfs
232
+ scan-ref: .venv
233
+ vuln-type: library
234
+ scanners: vuln
235
+ ```
236
+
237
+ ## `backend-common/reusable-semgrep.yaml`
238
+
239
+ Whole Semgrep job (runs in the `semgrep/semgrep` container, renders a job
240
+ summary, posts a PR comment). Calls `resolve-security-trigger` internally, so the
241
+ consumer stub only owns the `on:` triggers:
242
+
243
+ ```yaml
244
+ name: Semgrep CE scan
245
+ on:
246
+ pull_request: { types: [labeled] }
247
+ issue_comment: { types: [created] }
248
+ push: { branches: [main] }
249
+ workflow_dispatch: {}
250
+ permissions: { contents: read, pull-requests: write }
251
+ jobs:
252
+ semgrep:
253
+ uses: gigaverse-app/backend-common/.github/workflows/reusable-semgrep.yaml@<pinned-sha>
254
+ secrets: inherit
255
+ ```
256
+
257
+ When a consumer also runs Trivy (or another security scan), resolve once in the
258
+ consumer and pass `decision-precomputed: true` plus `should-run`, `ref`, and
259
+ `pr-number`. The internal resolver job then skips and both scans remain parallel.
260
+
261
+ The scan preserves `always()` so a precomputed decision can bypass that skipped
262
+ resolver, but explicitly excludes cancelled callers. Consumer workflow
263
+ concurrency can therefore stop obsolete Semgrep containers instead of making a
264
+ new PR head wait for the old scan to finish.
265
+
266
+ Override the rule set per repo via the `configs` / `exclude` / `exclude-rules`
267
+ inputs; defaults match the gigaverse-ai baseline. Prefer
268
+ `extra-exclude-rules` when a consumer only needs to append a repo-specific
269
+ exception while continuing to inherit the shared exclusions.
270
+
271
+ ## Why not also a reusable Trivy workflow?
272
+
273
+ The Semgrep scan is identical everywhere, so it ships as one workflow. Trivy's
274
+ **build** is not: `gigaverse-ai` builds N images from one parametrized
275
+ `deployment/Dockerfile` driven by a `.github/services/*.yaml` registry, while
276
+ `gigaverse-backend` builds a single image from `Dockerfile-backend`. Forcing both
277
+ into one workflow means conditional build inputs that leak each repo's layout.
278
+ Instead each repo keeps its own build/matrix and shares the two things that are
279
+ actually common: the trigger gate and the scan policy.
280
+
281
+ > This is backend-common's consumer policy, not CI-esta's versioning scheme.
282
+ > Consumers pin `PR<N>` while testing an open backend-common PR and immutable
283
+ > `v<N>` after it merges. The downstream bump workflow generates every
284
+ > backend-common reference from that one `pyproject.toml` revision (see
285
+ > `update_ci_esta.yaml`, `sync_backend_common_status.yaml`).
286
+
287
+ ## `backend-common/reusable-ci-code-validation.yaml`
288
+
289
+ Runs Actionlint, composite-action metadata validation, ShellCheck, targeted
290
+ Semgrep, and Trivy configuration/secret scanning against changed files under
291
+ caller-provided CI roots. The caller owns only event path filters and the
292
+ newline-delimited roots:
293
+
294
+ ```yaml
295
+ jobs:
296
+ validate:
297
+ uses: gigaverse-app/backend-common/.github/workflows/reusable-ci-code-validation.yaml@<consumer-ref>
298
+ with:
299
+ ci-paths: |
300
+ .github
301
+ scripts/ci
302
+ ```
303
+
304
+ The planning job validates roots, ignores deleted files, and passes the exact
305
+ added/modified file set to every checker. On PRs it starts at the newest fresh,
306
+ successful ancestor from the same workflow and same PR whose named evidence job
307
+ actually succeeded; failed, cancelled, skipped, stale, unrelated, or
308
+ non-ancestor runs are never reused. GitHub's PR `paths`
309
+ filter is cumulative, so the planner may still allocate after a prior CI change,
310
+ but an empty new delta skips the Semgrep container plus the lint and Trivy
311
+ steps. No secrets are accepted or inherited.
312
+
313
+ Backend-common's own `ci-code-validation.yaml` also invokes each composite action
314
+ through a relative `./.github/actions/...` reference. This is intentional:
315
+ consumer-facing calls stay pinned to known commits, while pull requests in this
316
+ repository exercise the implementation in the current checkout. Its reusable
317
+ call sets `run-checks: false`, reusing only the shared change plan so the same
318
+ files are not scanned once by pinned actions and again by local actions.
319
+
320
+ ## `setup-python-uv`
321
+
322
+ Centralizes backend-common's Python version, uv installation, dependency cache,
323
+ and development dependency installation. It syncs with `--locked`, so a
324
+ `pyproject.toml` edit that is not accompanied by a refreshed `uv.lock` fails the
325
+ build instead of silently resolving something new. It is local project
326
+ setup rather than a consumer-facing contract. The repository's test workflow is
327
+ also intentionally local. Push and pull-request runs are allowed to overlap:
328
+ guaranteed pytest execution is more important than deduplicating CI events.
329
+
330
+ This extracted action caches uv downloads and runs `uv sync --locked --all-groups`.
331
+ It does **not** implement Backend's separate `.venv` restore/validation/save
332
+ composite. Review the [cache boundaries](../../docs/ci-optimization.md#cache-the-right-thing)
333
+ before treating these similarly named actions as interchangeable.
334
+
335
+ ### Distributed pytest ranges
336
+
337
+ `ci-esta-pytest run-local --shards N --first-shard A --last-shard B -- ...` runs the inclusive range A–B from one global exact-file plan. Workers retain global `GV_PYTEST_SHARD_INDEX` and `GV_PYTEST_SHARD_COUNT`; logs and completion reports therefore remain disjoint across machines. Omitted bounds preserve the existing all-shards behavior. Invalid or empty ranges fail before starting a worker.
338
+
339
+ Pass the same bounds as `PLAN_FIRST_SHARD` and `PLAN_LAST_SHARD` to `verify-pytest-completion`. Each pool proves its entire range. The consumer must require every pool, verify that its ranges partition 1–N, and issue the reusable aggregate receipt only after all pools prove completion. A successful pool alone never authorizes reuse of the global plan.
@@ -0,0 +1,189 @@
1
+ name: Cancel obsolete PR workflow runs
2
+ description: >
3
+ Cancel active runs of this same workflow and PR when their head SHA is no
4
+ longer current. Call from an existing first-attempt job so the cancellation
5
+ closes the post-admission race without creating another billed runner job.
6
+
7
+ inputs:
8
+ pr-number:
9
+ description: Pull request whose current head is authoritative.
10
+ required: true
11
+ current-head-sha:
12
+ description: Current pull-request head SHA.
13
+ required: true
14
+ head-ref:
15
+ description: Same-repository pull-request head branch.
16
+ required: true
17
+ github-token:
18
+ description: Token with actions:write, checks:read and contents:read permissions in the calling job.
19
+ required: false
20
+ default: ${{ github.token }}
21
+
22
+ runs:
23
+ using: composite
24
+ steps:
25
+ - name: Cancel obsolete active runs
26
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
27
+ env:
28
+ PR_NUMBER: ${{ inputs.pr-number }}
29
+ CURRENT_HEAD_SHA: ${{ inputs.current-head-sha }}
30
+ HEAD_REF: ${{ inputs.head-ref }}
31
+ with:
32
+ github-token: ${{ inputs.github-token }}
33
+ script: |
34
+ const activeStatuses = new Set(["queued", "in_progress", "pending", "waiting"]);
35
+ const prNumber = Number(process.env.PR_NUMBER);
36
+ const currentHeadSha = process.env.CURRENT_HEAD_SHA;
37
+ const headRef = process.env.HEAD_REF;
38
+ const repoFullName = `${context.repo.owner}/${context.repo.repo}`;
39
+
40
+ // Inputs describe the triggering event, which can be stale by the
41
+ // time a queued matrix job starts. Branch reads use contents:read,
42
+ // already required for parent discovery; no new PR permission is needed.
43
+ const callerIsCurrent = async () => {
44
+ const { data: branch } = await github.rest.repos.getBranch({
45
+ owner: context.repo.owner,
46
+ repo: context.repo.repo,
47
+ branch: headRef,
48
+ });
49
+ return branch.commit.sha === currentHeadSha;
50
+ };
51
+
52
+ try {
53
+ if (!(await callerIsCurrent())) {
54
+ core.info("Skipping obsolete-run cleanup from a stale event head");
55
+ return;
56
+ }
57
+ const { data: currentRun } = await github.rest.actions.getWorkflowRun({
58
+ owner: context.repo.owner,
59
+ repo: context.repo.repo,
60
+ run_id: context.runId,
61
+ });
62
+ const obsoleteRuns = new Map();
63
+ const addIfObsolete = (run) => {
64
+ const samePullRequest = (run.pull_requests || []).some((pull) => pull.number === prNumber);
65
+ const sameRepositoryBranch =
66
+ run.head_repository?.full_name === repoFullName && run.head_branch === headRef;
67
+ if (
68
+ run.id !== context.runId &&
69
+ run.workflow_id === currentRun.workflow_id &&
70
+ // A push can arrive during discovery. Run numbers are immutable
71
+ // within one workflow, including reruns, so newer runs are never victims.
72
+ Number.isSafeInteger(run.run_number) &&
73
+ Number.isSafeInteger(currentRun.run_number) &&
74
+ run.run_number < currentRun.run_number &&
75
+ activeStatuses.has(run.status) &&
76
+ run.head_sha !== currentHeadSha &&
77
+ (samePullRequest || sameRepositoryBranch)
78
+ ) {
79
+ obsoleteRuns.set(run.id, run);
80
+ }
81
+ };
82
+
83
+ // The first parent is the previous PR head for the normal one-commit
84
+ // agent push. Its check runs expose run IDs immediately, while the
85
+ // workflow-run list can lag behind the synchronize event.
86
+ const candidateHeadShas = new Set();
87
+ if (context.payload.before && context.payload.before !== currentHeadSha) {
88
+ candidateHeadShas.add(context.payload.before);
89
+ }
90
+ try {
91
+ const { data: currentCommit } = await github.rest.repos.getCommit({
92
+ owner: context.repo.owner,
93
+ repo: context.repo.repo,
94
+ ref: currentHeadSha,
95
+ });
96
+ const firstParentSha = currentCommit.parents[0]?.sha;
97
+ if (firstParentSha) {
98
+ candidateHeadShas.add(firstParentSha);
99
+ }
100
+ } catch (error) {
101
+ core.warning(`Could not resolve current head's first parent: ${error.message}`);
102
+ }
103
+
104
+ for (const candidateHeadSha of candidateHeadShas) {
105
+ const { data: checkResponse } = await github.rest.checks.listForRef({
106
+ owner: context.repo.owner,
107
+ repo: context.repo.repo,
108
+ ref: candidateHeadSha,
109
+ per_page: 100,
110
+ });
111
+ const checkRuns = checkResponse.check_runs;
112
+ const candidateRunIds = new Set();
113
+ for (const checkRun of checkRuns) {
114
+ const match = checkRun.details_url?.match(/\/actions\/runs\/(\d+)/);
115
+ // A completed guard check can belong to a workflow whose next
116
+ // expensive job is only queued. Check state is not run state.
117
+ if (match) {
118
+ candidateRunIds.add(Number(match[1]));
119
+ }
120
+ }
121
+ for (const runId of candidateRunIds) {
122
+ try {
123
+ const { data: run } = await github.rest.actions.getWorkflowRun({
124
+ owner: context.repo.owner,
125
+ repo: context.repo.repo,
126
+ run_id: runId,
127
+ });
128
+ addIfObsolete(run);
129
+ } catch (error) {
130
+ core.warning(`Could not inspect previous-head run ${runId}: ${error.message}`);
131
+ }
132
+ }
133
+ }
134
+
135
+ // Fallback covers reopened PRs and runs already visible in the
136
+ // workflow index. The map de-duplicates both discovery paths.
137
+ const indexedRuns = await github.paginate(github.rest.actions.listWorkflowRuns, {
138
+ owner: context.repo.owner,
139
+ repo: context.repo.repo,
140
+ workflow_id: currentRun.workflow_id,
141
+ event: "pull_request",
142
+ branch: headRef,
143
+ per_page: 100,
144
+ });
145
+ for (const run of indexedRuns) {
146
+ addIfObsolete(run);
147
+ }
148
+ core.info(`Found ${obsoleteRuns.size} obsolete active run(s)`);
149
+
150
+ for (const run of obsoleteRuns.values()) {
151
+ if (!(await callerIsCurrent())) {
152
+ core.info("Stopping obsolete-run cleanup because the branch advanced");
153
+ return;
154
+ }
155
+ try {
156
+ await github.rest.actions.cancelWorkflowRun({
157
+ owner: context.repo.owner,
158
+ repo: context.repo.repo,
159
+ run_id: run.id,
160
+ });
161
+ await new Promise((resolve) => setTimeout(resolve, 2000));
162
+ const { data: afterCancel } = await github.rest.actions.getWorkflowRun({
163
+ owner: context.repo.owner,
164
+ repo: context.repo.repo,
165
+ run_id: run.id,
166
+ });
167
+ if (activeStatuses.has(afterCancel.status)) {
168
+ await github.rest.actions.forceCancelWorkflowRun({
169
+ owner: context.repo.owner,
170
+ repo: context.repo.repo,
171
+ run_id: run.id,
172
+ });
173
+ core.notice(
174
+ `Force-cancelled obsolete ${run.head_sha} run ${run.id}; PR #${prNumber} is ${currentHeadSha}`,
175
+ );
176
+ } else {
177
+ core.notice(`Cancelled obsolete ${run.head_sha} run ${run.id}; PR #${prNumber} is ${currentHeadSha}`);
178
+ }
179
+ } catch (error) {
180
+ // A run may finish between listing and cancellation. Validation
181
+ // remains authoritative even when cost cleanup races or fails.
182
+ core.warning(`Could not cancel obsolete run ${run.id}: ${error.message}`);
183
+ }
184
+ }
185
+ } catch (error) {
186
+ // Cost cleanup is fail-open: an API outage must not suppress the
187
+ // current head's terminal validation.
188
+ core.warning(`Could not inspect obsolete workflow runs: ${error.message}`);
189
+ }