Skip to content

ci: make path-filtered required gates always report (unblock stuck PRs)#216

Merged
hyperpolymath merged 1 commit into
mainfrom
claude/ci-required-check-skip-shim
Jun 13, 2026
Merged

ci: make path-filtered required gates always report (unblock stuck PRs)#216
hyperpolymath merged 1 commit into
mainfrom
claude/ci-required-check-skip-shim

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

What this fixes

PRs #213 (dependabot flake.lock) and #215 (scripts-only) are stuck at mergeable_state: blocked despite every check green (and #213 approved). Root cause: seven gates are workflow-level path-filtered (on.*.paths). When a PR touches none of a gate's paths the workflow never runs, so its required status check stays "Expected" forever → permanent block.

The asymmetry this exploits: a path-filtered workflow that never runs blocks; a job skipped via if: reports SUCCESS to required checks. This converts the former into the latter.

Change (uniform across all 7)

abi-drift, backend-assurance, e2e, lsp-dap-bsp, proofs, truthfulness, zig-test:

  1. Drop the on.*.paths filter → the workflow always runs, so the required check is always created.
  2. Add a lightweight changes job that recomputes the gate's original path set via git diff origin/<base>...HEAD (the same pattern abi-drift/zig-test already use internally).
  3. Gate every heavy job with needs: changes + if: needs.changes.outputs.run == 'true'. Nothing relevant changed → heavy job skipped → passes → PR unblocked and no wasted CI.

No branch-protection change needed — job/check names are unchanged. workflow_dispatch added to all 7 for manual full runs.

Safety

  • Fail-safe toward running: the detector defaults run=true and only sets false when a successful diff shows no relevant path changed (any fetch/diff error or unknown base ⇒ run). The dangerous direction — skipping a gate that should run — can't happen on detection failure.
  • Each regex mirrors the gate's original paths:, so a relevant change runs it exactly as before (verified: 77/77 unit checks — abi/→proofs+abi-drift, ffi/→zig+truthfulness+abi-drift, SafetyLemmas.idr→backend-assurance+proofs, etc.; chore(deps): bump nixpkgs from 8c3cede to 8c91a71 #213/feat(scripts): extend §1.5 verb-canary coverage (Phase E) #215 → all skip).

Validation

actionlint -shellcheck=  (all 7)        → EXIT 0   (structure/expressions/needs-graph valid)
actionlint (all 7)                      → only PRE-EXISTING shellcheck infos in original run-scripts (untouched)
detection regexes (77 checks)           → 77/77 correct
gated heavy jobs                        → 15/15 (abi-drift 1, backend-assurance 1, e2e 5, lsp-dap-bsp 4, proofs 2, truthfulness 1, zig-test 1)
leftover path filters                   → 0

Self-validating: editing a workflow file no longer self-triggers its heavy gate (kept out of each regex), so this PR's own checks exercise the skip path — the gates should report skipped/success here, demonstrating the unblock live.

Draft — why

I can't runtime-test the run=true branch (it needs the GH runners), and this changes how required gates fire, so it wants your eyes before merge. Mark ready / squash-merge when you're satisfied. After it lands, re-check #213/#215 — they should flip from blocked to mergeable.

Out of scope (flagged, not fixed): the pre-existing shellcheck infos in abi-drift/zig-test original scripts, and the Hypatia baseline backlog (stale GEMINI.md, unpinned governance.yml action, missing_timeout_minutes on several workflows) — these are non-blocking and belong in a separate hygiene pass.

https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa


Generated by Claude Code

Seven gates were workflow-level path-filtered (on.*.paths). When a PR
touched none of a gate's paths the workflow never ran, so its REQUIRED
status check stayed "Expected" forever and the PR sat at
mergeable_state=blocked — even with every other check green and the PR
approved (e.g. #213 flake.lock bump, #215 scripts-only). The asymmetry
this fixes: a path-filtered workflow that never runs BLOCKS, whereas a
job skipped via `if:` reports SUCCESS to required checks.

For each of abi-drift, backend-assurance, e2e, lsp-dap-bsp, proofs,
truthfulness, zig-test:
  * drop the workflow-level `on.*.paths` filter (always run, so the
    required check is always created),
  * add a lightweight `changes` job that recomputes the gate's original
    path set via `git diff origin/<base>...HEAD` (the same pattern
    abi-drift/zig-test already used internally for per-cartridge scoping),
  * gate every heavy job with `needs: changes` +
    `if: needs.changes.outputs.run == 'true'`. When nothing relevant
    changed the heavy job is skipped (a passing required check), so
    unrelated PRs are no longer blocked AND don't pay for the heavy work.

Fail-safe: the detector defaults to run=true and only skips when a
SUCCESSFUL diff shows no relevant path changed (any fetch/diff error or
unknown base => run). The dangerous direction — skipping a gate that
should run — cannot happen on detection failure. Each regex mirrors the
gate's original `paths:`, so a relevant change runs it exactly as before.

No branch-protection change needed: job/check names are unchanged.
workflow_dispatch added to all 7 for manual full runs.

Validated locally: actionlint clean on all 7 (only pre-existing
shellcheck infos remain, untouched); detection regexes unit-tested
against representative change-sets (77/77, incl. #213/#215 -> skip and
abi/ffi/src changes -> run).

Note: editing a workflow file no longer self-triggers its heavy gate
(kept out of each regex), so workflow-only PRs like THIS one exercise the
skip path and stay green.

https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa
@github-actions

Copy link
Copy Markdown

🔍 Hypatia Security Scan

Findings: 272 issues detected

Severity Count
🔴 Critical 15
🟠 High 138
🟡 Medium 119

⚠️ Action Required: Critical security issues found!

View findings
[
  {
    "reason": "Stale AI session file -- delete",
    "type": "stale",
    "file": "GEMINI.md",
    "action": "delete",
    "rule_module": "root_hygiene",
    "severity": "medium"
  },
  {
    "reason": "Issue in abi-drift.yml",
    "type": "missing_timeout_minutes",
    "file": "abi-drift.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in codeql.yml",
    "type": "missing_timeout_minutes",
    "file": "codeql.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in container-publish.yml",
    "type": "missing_timeout_minutes",
    "file": "container-publish.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  },
  {
    "reason": "Issue in dogfood-gate.yml",
    "type": "missing_timeout_minutes",
    "file": "dogfood-gate.yml",
    "action": "flag",
    "rule_module": "workflow_audit",
    "severity": "medium"
  }
]

Powered by Hypatia Neurosymbolic CI/CD Intelligence

@hyperpolymath hyperpolymath marked this pull request as ready for review June 13, 2026 12:01
@hyperpolymath hyperpolymath merged commit ece30ad into main Jun 13, 2026
48 checks passed
@hyperpolymath hyperpolymath deleted the claude/ci-required-check-skip-shim branch June 13, 2026 12:01
hyperpolymath added a commit that referenced this pull request Jun 13, 2026
…219)

Documentation-only follow-up to #216 — captures the required-gate
"skip-shim" as a durable convention so it can't be silently regressed,
and records the work in machine-readable state. **No workflow or code
changes.**

## What's added
- **`docs/AI-CONVENTIONS.adoc`** — new "CI / Required Status Checks"
section (the do/don't rule). Also fixes a **stale** Banned-Languages
row: `TypeScript → AffineScript` (not ReScript — retired as the
destination 2026-04-30, per CLAUDE.md).
- **`.claude/CLAUDE.md`** — matching "CI / Required Status Checks"
section for AI agents.
- **`docs/wikis/CI-and-Required-Checks.adoc`** — new developer wiki
page: the problem (path-filtered required check → permanent "Expected" →
blocked PR), the fix, a copy-paste workflow template, a do/don't table,
and the green-but-blocked diagnostic. Linked from `Home.adoc`.
- **`.machine_readable/6a2/STATE.a2ml`** — 2026-06-13 session entry
(#216 + merged #213/#215 + follow-ups #218/#46/#47/#48); `last-updated`
bumped.
- **`.machine_readable/6a2/PLAYBOOK.a2ml`** — `[ci-required-gates]`
runbook section.
- **`CONTRIBUTING.md`** — brief pointer note.

Both a2ml files verified to still parse as TOML. Draft for your review.

https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa

---
_Generated by [Claude
Code](https://claude.ai/code/session_019tMcRS1Dm1nWjjYP4WvbJa)_

Co-authored-by: Claude <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Jun 20, 2026
…229)

## Summary

Wires `scripts/hcg-surface-drift-check.sh` (landed in boj-server#228,
merged 2026-06-19) into GitHub Actions, so the surface⊆policy invariant
the ADR calls its largest declared risk is re-proven on every PR rather
than relying on the manual re-verification stamp in
`config/gateway-policy-boj.yaml`'s header.

PR #228 explicitly flagged this CI wiring as the follow-up step — "a CI
wiring PR should follow [the always-trigger + changes-job] pattern. Out
of scope here." This is that follow-up; the script, the router, and the
policy are unchanged.

## What lands

A single new file: `.github/workflows/hcg-surface-drift.yml`. The
workflow follows the boj-server "always-trigger + changes-job" pattern
documented in `docs/wikis/CI-and-Required-Checks.adoc` and
`.claude/CLAUDE.md` §"CI / Required Status Checks":

- **No `on.*.paths`** — the check is always created. A path-filtered
required workflow that never fires is the failure mode that stranded
#213/#215 until #216 fixed it; this gate is built to never re-introduce
it, regardless of whether it later joins `required_status_checks`.
- **Lightweight `changes` job** recomputes relevance via `git diff
origin/<base>...HEAD` against the four paths this gate cares about —
router (`elixir/lib/boj_rest/router.ex`), live policy
(`config/gateway-policy-boj.yaml`), the drift script
(`scripts/hcg-surface-drift-check.sh`), and the workflow file itself.
Fail-safe to `run=true` on any diff failure.
- **Heavy `check` job** is `needs: changes` + `if:
needs.changes.outputs.run == 'true'`. A skipped `if:` reports SUCCESS to
any future required-context list, so unrelated PRs never pay for it and
can never be blocked by it.
- **Pinned action**, **timeout-minutes**, **concurrency group**,
**`permissions: contents: read`**, **SPDX header** — matches the
canonical pattern in `.github/workflows/abi-drift.yml`.

The `check` job invokes the script with `bash
scripts/hcg-surface-drift-check.sh -v` (matching the test plan in #228)
so it works regardless of the script's file mode — #228 committed the
script as 0644.

## What this PR does NOT do

- **Does NOT** modify the runbook §1.5 ("Gateway-side prerequisites").
Adoption of the CI gate into the §1.5 checklist is a one-line
owner-driven runbook update — the PR #228 deliberate boundary stays in
place.
- **Does NOT** add the new check to `.github/settings.yml`'s
`required_status_checks` list (currently `hypatia-scan` + `codeql`).
Promotion to required is a settings change for the owner to make once
the gate has run green on a few PRs.
- **Does NOT** modify the live policy, the example policy, the router,
the script, or any other Phase E artefact. The change is wholly within
`.github/workflows/`.
- **Does NOT** pre-empt the §6.4 Trustfile flip (`tier_2_gateway.status`
stays `PENDING`), the staging soak (§3.3), or cerro-torre `.ctp` signing
— all of which remain owner-driven per the channel doctrine reaffirmed
in #207 / #224.
- Per the single-lane HCG channel discipline (pattern set in
`http-capability-gateway` PRs #14, #22, #26, #30, #38 and `boj-server`
PRs #168, #173, #224, #226, #228): joint-close is owner-only. **This PR
refs but does not close `standards#100`.**

## Channel state note

This session could not read `hyperpolymath/standards#91` / `#100` (the
session's MCP repo scope is restricted to `http-capability-gateway` and
`boj-server`), so the brief's instructed status comment on
`standards#91` could not be posted. State was reconstructed from the
canonical sources in this repo (ADR-0004, the integration plan, the
audit, the rollout runbook, the live policy,
`docs/wikis/CI-and-Required-Checks.adoc`) plus the merged-PR history of
both in-scope repos. Analysis: Phase A/B/C/D closed; Phase E
(`standards#100`) is the only open phase; #228 (2026-06-19) is the most
recent advance and explicitly named this CI wiring as the next step.

## Test plan

- [ ] **Required**: the `changes` job runs and emits `run=true` (because
`.github/workflows/hcg-surface-drift.yml` matches the path regex), so
the `check` job is gated through, not skipped, on this PR.
- [ ] **Required**: the `check` job runs `bash
scripts/hcg-surface-drift-check.sh -v` and exits 0 with the OK message —
current `main` (64a70c5) has 7 wired routes, 28 policy rules, no drift;
locally re-verified on this branch.
- [ ] **Synthetic skip**: on a follow-up PR that touches none of the
four watched paths, `changes.outputs.run` is `false` and `check` reports
`skipped` (which counts as success for any required-context list).
- [ ] **Synthetic drift**: a temporary PR adding `get "/__drift_test__"`
to `elixir/lib/boj_rest/router.ex` without a matching policy rule fires
`run=true`, `check` exits 1 with the route listed under `DRIFT:`, and
the PR is blocked from merge if/when this gate is promoted to required.
- [ ] No `actionlint` / Hypatia / SPDX gate fires on the new workflow
file.

Refs hyperpolymath/standards#91
Refs hyperpolymath/standards#100

🤖 Generated with [Claude Code](https://claude.com/claude-code)


---
_Generated by [Claude
Code](https://claude.ai/code/session_019cKmxx6AkNjzhXT6ZoxGfx)_

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants