Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,17 @@ jobs:
- uses: ./.github/actions/setup-uv
with:
python-version: "3.12"
# OMN-15241: reject-required-check-skip-vector is SKIPped in CI *only*.
# It is a LOCAL shift-left wrapper that resolves the canonical validator
# from $OMNI_HOME/omniclaude, which does not exist on a GitHub-hosted
# runner -- it would hard-error here for a missing sibling clone, not for
# a real skip vector. ZERO enforcement is lost: the identical validator
# runs in CI on every PR via .github/workflows/required-check-skip-guard-caller.yml
# (context `required-check-skip-guard / check-skip-vectors`). This is a
# duplicate-invocation removal, not a gate bypass.
- name: Run full pre-commit
if: needs.zone-filter.outputs.docs_only != 'true' && (github.event_name != 'pull_request' || github.base_ref != 'dev')
run: SKIP=check-receipt-honesty,check-receipt-hardening uv run pre-commit run --all-files
run: SKIP=check-receipt-honesty,check-receipt-hardening,reject-required-check-skip-vector uv run pre-commit run --all-files
- name: Run dev PR changed-file pre-commit
if: needs.zone-filter.outputs.docs_only != 'true' && github.event_name == 'pull_request' && github.base_ref == 'dev'
env:
Expand All @@ -55,7 +63,7 @@ jobs:
echo "Dev PR fast-lane: no changed files for pre-commit."
exit 0
fi
SKIP=check-receipt-honesty,check-receipt-hardening uv run pre-commit run --files "${changed_files[@]}"
SKIP=check-receipt-honesty,check-receipt-hardening,reject-required-check-skip-vector uv run pre-commit run --files "${changed_files[@]}"
- name: Run evidence-only pre-commit
if: needs.zone-filter.outputs.docs_only == 'true'
env:
Expand All @@ -72,7 +80,7 @@ jobs:
echo "Evidence-only fast-lane: no changed contract or receipt YAML files."
exit 0
fi
SKIP=check-receipt-honesty,check-receipt-hardening uv run pre-commit run --files "${changed_files[@]}"
SKIP=check-receipt-honesty,check-receipt-hardening,reject-required-check-skip-vector uv run pre-commit run --files "${changed_files[@]}"
- name: Run receipt hardening on changed receipts
env:
BASE_REF: ${{ github.base_ref || github.event.merge_group.base_ref || 'dev' }}
Expand Down
22 changes: 22 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,28 @@ repos:
always_run: true
stages: [pre-commit]
files: ^(\.pre-commit-config\.yaml|\.github/workflows/ci\.yml)$
# OMN-15241: Required-Check Skip-Vector Guard (canary OMN-14854) -- LOCAL
# parity for a guard this repo already runs CI-only via
# .github/workflows/required-check-skip-guard-caller.yml. Without this hook a
# skip-vector violation commits with every local hook GREEN and is caught only
# after push -- detection without local enforcement (root CLAUDE.md Rule #5).
#
# Fails closed when a workflow file or the required-checks manifest introduces
# any of the five skip vectors on a REQUIRED context: a path-scoped trigger, an
# ungated job-level `if:`, an ungated reusable-caller `if:`, a missing
# pull_request/merge_group trigger, or a `needs:` with no `if:` that provably
# runs regardless of the needs result (OMN-15057 vector 5).
#
# The wrapper executes the SAME validator the CI reusable workflow executes
# (canonical copy in omniclaude, resolved via $OMNI_HOME), so local and CI
# verdicts cannot diverge on validator logic.
- id: reject-required-check-skip-vector
name: Reject skip vectors on required-check workflows (OMN-15241)
entry: .pre-commit-hooks/reject-required-check-skip-vector.sh
language: script
pass_filenames: false
files: '^\.github/(workflows/.*\.ya?ml|required-checks\.yaml)$'
stages: [pre-commit]
# Configuration
# OMN-14672 (DRIFT-2a fix): every stage a hook uses MUST be installed by
# `pre-commit install`. Previously [pre-commit, pre-push] only, so the
Expand Down
87 changes: 87 additions & 0 deletions .pre-commit-hooks/reject-required-check-skip-vector.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
#
# OMN-15241 (fan-out of the OMN-14854 omniclaude canary via the OMN-14865
# omnibase_infra pattern): Required-Check Skip-Vector Guard -- local
# (pre-commit) run.
#
# WHY THIS EXISTS: this repo already calls the guard in CI
# (.github/workflows/required-check-skip-guard-caller.yml) but had no local
# hook, so a skip-vector violation committed with every local hook GREEN and
# was caught CI-only -- the detection-without-local-enforcement class root
# CLAUDE.md Rule #5 exists to kill.
#
# DRY, NOT a re-implementation: omniclaude is the canonical source for
# validate_no_required_check_skip_vectors.py (its own reusable workflow says
# so: all repos call it via `uses:` instead of running their own copy). This
# wrapper executes that SAME file, resolved from the OMNI_HOME sibling clone
# -- identical to omnibase_infra's copy of this hook. Local and CI verdicts
# cannot diverge on validator logic, because both run the identical script
# against this repo's own .github/required-checks.yaml manifest and
# .github/workflows/ directory.
#
# FAIL-LOUD (root CLAUDE.md Rule #8, and the fail-loud pre-commit meta-gate):
# if OMNI_HOME is unset or the sibling omniclaude clone does not contain the
# validator, this hook hard-errors (exit 1) with remediation. It never
# degrades to a green skip -- a gate that cannot run must be
# byte-indistinguishable from a failing gate.
#
# Honest drift note: CI resolves the validator at the omniclaude ref pinned in
# required-check-skip-guard-caller.yml; this local hook runs whatever is
# currently checked out in $OMNI_HOME/omniclaude (tracking dev). A same-session
# `git -C "$OMNI_HOME/omniclaude" pull --ff-only` keeps them converged.

set -euo pipefail

if REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" && [[ -n "$REPO_ROOT" ]]; then
:
else
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
fi

if [[ -z "${OMNI_HOME:-}" ]]; then
echo "ERROR: OMNI_HOME is not set. required-check-skip-guard needs the" >&2
echo "canonical validator from the omniclaude sibling clone at" >&2
# Deliberately single-quoted/literal, not an expansion.
# shellcheck disable=SC2016
echo '$OMNI_HOME/omniclaude -- set OMNI_HOME to the omni_home path.' >&2
exit 1
fi

VALIDATOR="${OMNI_HOME}/omniclaude/.github/actions/required-check-skip-guard/validate_no_required_check_skip_vectors.py"

if [[ ! -f "${VALIDATOR}" ]]; then
echo "ERROR: canonical validator missing at ${VALIDATOR}." >&2
echo "Ensure \$OMNI_HOME/omniclaude is a checked-out clone containing" >&2
echo ".github/actions/required-check-skip-guard/validate_no_required_check_skip_vectors.py" >&2
echo "(git -C \"\$OMNI_HOME/omniclaude\" pull --ff-only)." >&2
exit 1
fi

# Interpreter selection. The validator needs PyYAML. A bare `python3` from PATH
# is NOT guaranteed to have it (a uv-managed shim on PATH commonly does not),
# and a hook that hard-errors on every commit for a missing third-party import
# is a hook developers rip out. Order: explicit override -> a python3 that can
# actually import yaml -> uv's ephemeral env. No silent degrade at any step:
# if none of the three resolve, this exits 1.
run_validator() {
"$@" "${VALIDATOR}" \
--manifest "${REPO_ROOT}/.github/required-checks.yaml" \
--workflows-dir "${REPO_ROOT}/.github/workflows"
}

if [[ -n "${PYTHON_BIN:-}" ]]; then
exec_cmd=("${PYTHON_BIN}")
elif command -v python3 >/dev/null 2>&1 && python3 -c 'import yaml' >/dev/null 2>&1; then
exec_cmd=(python3)
elif command -v uv >/dev/null 2>&1; then
exec_cmd=(uv run --no-project --with pyyaml python)
else
echo "ERROR: required-check-skip-guard cannot resolve a Python with PyYAML." >&2
echo "Install PyYAML for python3, install uv, or set PYTHON_BIN to an" >&2
echo "interpreter that can import yaml. This gate does not degrade." >&2
exit 1
fi

run_validator "${exec_cmd[@]}"
Loading