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
12 changes: 7 additions & 5 deletions .github/workflows/public-repo-hygiene.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
# private address, an operator machine path, a private repository name, and
# internal-KB prose in a public README.
#
# mode: report. The gate records every finding and exits 0, so it lands without
# blocking unrelated PRs on a repository that carries pre-existing residue. It
# flips to enforce when that residue is FIXED -- never by moving it into
# .public-repo-hygiene.yaml.
# mode: report, with the five content classes enforced on ADDED lines
# (OMN-20782): .public-repo-hygiene.yaml sets enforce_scope and enforce_classes,
# the reusable resolves the pull request's diff base itself, and a PR that adds
# a line in one of those classes goes red while one that does not stays green
# over the pre-existing residue. The whole-tree mode flips to enforce when that
# residue is FIXED -- never by moving it into .public-repo-hygiene.yaml.
#
# Layer (a) reads .public-repo-hygiene.yaml at this repo's root; the gate
# refuses to run without it rather than treating an absent config as an empty
Expand Down Expand Up @@ -42,7 +44,7 @@

jobs:
public-repo-hygiene:
uses: OmniNode-ai/omniclaude/.github/workflows/public-repo-hygiene-reusable.yml@3b8addff365b1a53e04106284b9275b6d5fad3c6
uses: OmniNode-ai/omniclaude/.github/workflows/public-repo-hygiene-reusable.yml@6e47aa311ac5d177d116d9597b07966a233d2463
with:
mode: report
secrets: inherit

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {}
10 changes: 10 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2445,6 +2445,16 @@ repos:
name: Skip count baseline can only shrink (OMN-19677)
args: [--baseline, config/skip_count_baseline.yaml, --parser, 'yaml-counted-list-map:suites', --base-ref, origin/dev]
files: ^config/skip_count_baseline\.yaml$
# OMN-20782: the public-repo hygiene gate over the lines a commit adds, at the
# same omniclaude revision as .github/workflows/public-repo-hygiene.yml, so the
# local verdict and the CI verdict cannot diverge. The hook reads the private
# denylist from the file named by OMNI_HYGIENE_VOCAB_PATH, with the lab
# vocabulary beside it, and fails closed without them.
- repo: https://github.com/OmniNode-ai/omniclaude
rev: 6e47aa311ac5d177d116d9597b07966a233d2463
hooks:
- id: public-repo-hygiene
require_serial: true
# OMN-19666: VCS-backed additional_dependencies can inherit GIT_INDEX_FILE
# during a cold-cache commit and overwrite the committing worktree index.
- repo: local
Expand Down
15 changes: 13 additions & 2 deletions .public-repo-hygiene.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,23 @@
# drift — a finding, not a root entry. Fix or delete it; do not
# add it here.
#
# mode: report — the gate records every finding with a per-class count and
# exits 0. It flips to enforce when the residue is FIXED, never by moving the
# mode: report — the gate records every finding with a per-class count. It
# flips to whole-tree enforce when the residue is FIXED, never by moving the
# residue into this file: that is the recurrence mechanism the OMN-17992
# programme exists to remove.
#
# enforce_scope / enforce_classes (OMN-20782, the OMN-19835 mechanism): the five
# content classes below fail the run, in CI and in the pre-commit hook, on the
# lines a change ADDS. The whole-tree residue stays reported on every run.
mode: report
suppression_registry: ".public-repo-hygiene-suppressions.yaml"
enforce_scope: added-lines
enforce_classes:
- "private-repo-name"
- "internal-kb-prose"
- "lab-config"
- "person-name"
- "private-network"
allowed_top_level:
- ".claude"
- ".env.example"
Expand Down
48 changes: 48 additions & 0 deletions contracts/OMN-20782.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
schema_version: "1.0.0"
ticket_id: OMN-20782
title: omnibase_infra enforces the five hygiene classes on added lines
summary: .public-repo-hygiene.yaml declares enforce_scope added-lines and the five enforce_classes, the public-repo-hygiene caller moves to an omniclaude revision that supports added-lines enforcement, and a pre-commit hook runs the gate at the same revision over staged added lines. No allowlist entry is added.
is_seam_ticket: false
interface_change: false
interfaces_touched: []
requirements:
- id: req-omn20782
statement: A pull request or a commit that adds a line in one of the five enforced hygiene classes fails the gate, and a change that touches no enforced class passes.
acceptance:
- id: AC1
statement: 'AC1: a pull request adding a lab-config literal fails the gate in CI, at the moved pin -- falsifier: the gate''s run on a scratch branch with one planted literal'
- id: AC2
statement: 'AC2: the pre-commit hook refuses the same staged literal -- falsifier: `pre-commit run public-repo-hygiene --files the planted file` exits non-zero'
- id: AC3
statement: 'AC3: a pull request touching no enforced class passes -- falsifier: this task''s own PR runs the gate green'
evidence_requirements:
- kind: tests
description: The config enforces the five classes on added lines, the caller is pinned to the added-lines revision, and a recorded run of the real gate at that pin fails a planted lab-config literal.
command: uv run pytest tests/ci/test_public_repo_hygiene_enforcement_omn20782.py -q
- kind: manual
description: The scratch pull request run, the local pre-commit refusal of the planted file and its positive control are quoted in the PR body; AC3 is this pull request's own public-repo-hygiene run.
command: pre-commit run public-repo-hygiene --files <planted file>
emergency_bypass:
enabled: false
justification: ''
follow_up_ticket_id: ''
dod_evidence:
- id: dod-omn-20782-ac1
description: The config enforces the five classes on added lines, the caller workflow is pinned to the added-lines revision, and the recorded run of the gate at that pin fails a planted lab-config literal.
source: manual
checks:
- check_type: test_passes
check_value: uv run pytest tests/ci/test_public_repo_hygiene_enforcement_omn20782.py -q -k "config_enforces_the_five_classes or caller_workflow_is_pinned or planted_lab_config_literal"
cwd: ${OMNI_HOME}/omnibase_infra
binds_ac:
- AC1
- id: dod-omn-20782-precommit-and-sweep
description: The pre-commit hook runs the gate at the same revision, the recorded `pre-commit run public-repo-hygiene` over a staged planted literal exits 1 (AC2), and a red public-repo-hygiene row fails the CI Summary sweep while a green one passes (AC3).
source: manual
checks:
- check_type: test_passes
check_value: uv run pytest tests/ci/test_public_repo_hygiene_enforcement_omn20782.py -q -k "precommit_runs_the_gate_hook or planted_lab_config_literal or ci_summary_sweep"
cwd: ${OMNI_HOME}/omnibase_infra
binds_ac:
- AC2
- AC3
15 changes: 15 additions & 0 deletions tests/ci/fixtures/public_repo_hygiene_added_lines_omn20782.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"_provenance": {
"source": "omniclaude scripts/public_repo_hygiene_gate.py at 6e47aa311ac5d177d116d9597b07966a233d2463, run by the exported public-repo-hygiene pre-commit hook over a staged planted lab-config literal in this repository",
"captured_utc": "2026-10-09T18:07:47Z"
},
"response": {
"gate_rev": "6e47aa311ac5d177d116d9597b07966a233d2463",
"command": "pre-commit run public-repo-hygiene --files tests/planted_omn20782_probe.txt",
"exit_code": 1,
"enforce_scope_line": "ENFORCE SCOPE: added lines (index over 73f2a806510d). 1 enforced finding(s) on lines this change adds fail the run; 14956 other finding(s) are reported only.",
"added_findings": [
"ADDED tests/planted_omn20782_probe.txt:1: lab-config"
]
}
}
7 changes: 6 additions & 1 deletion tests/ci/test_anti_growth_baselines_omn19677.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,12 @@ def _load_yaml(path: Path) -> dict[str, Any]:

def _omniclaude_repo() -> dict[str, Any]:
repos = _load_yaml(PRECOMMIT_CONFIG)["repos"]
matches = [repo for repo in repos if repo.get("repo") == OMNICLAUDE_REPO]
matches = [
repo
for repo in repos
if repo.get("repo") == OMNICLAUDE_REPO
and any(hook.get("id") == "anti-growth-baseline" for hook in repo["hooks"])
]
assert len(matches) == 1
return matches[0]

Expand Down
155 changes: 155 additions & 0 deletions tests/ci/test_public_repo_hygiene_enforcement_omn20782.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
"""OMN-20782: omnibase_infra enforces the five hygiene classes on added lines.

The public-repo hygiene gate blocks a class only when the repository's own
``.public-repo-hygiene.yaml`` lists it in ``enforce_classes`` (and, for the
added-lines scope, declares ``enforce_scope: added-lines``). Until this change
the file declared neither, so the gate could not fail a pull request. The
caller workflow and the pre-commit hook must sit on the same omniclaude
revision, which must carry the added-lines scope (omniclaude 9a58ef705) and the
env-var vocabulary path (omniclaude 6e47aa311).

Falsifier: restore the previous config, pin, or drop the pre-commit hook and the
named test fails.
"""

from __future__ import annotations

from datetime import UTC, datetime
from pathlib import Path
from typing import cast

import pytest
import yaml

from scripts.ci.ci_summary_gate import (
EXTERNAL_SWEEP_EXCLUSIONS,
evaluate_external_sweep,
)

pytestmark = pytest.mark.unit

REPO_ROOT = Path(__file__).resolve().parents[2]
CONFIG = REPO_ROOT / ".public-repo-hygiene.yaml"
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "public-repo-hygiene.yml"
PRECOMMIT = REPO_ROOT / ".pre-commit-config.yaml"

# omniclaude dev 6e47aa311, a descendant of 9a58ef705 (added-lines scope).
GATE_REV = "6e47aa311ac5d177d116d9597b07966a233d2463"
# The pin before this change: it carries the added-lines scope but not the
# env-var vocabulary path the exported pre-commit hook reads.
PREVIOUS_REV = "3b8addff365b1a53e04106284b9275b6d5fad3c6"
FIVE_CLASSES = {
"private-repo-name",
"internal-kb-prose",
"lab-config",
"person-name",
"private-network",
}
REUSABLE = "OmniNode-ai/omniclaude/.github/workflows/public-repo-hygiene-reusable.yml"
HYGIENE_CONTEXT = "public-repo-hygiene / public-repo-hygiene"
NOW = datetime(2026, 10, 9, 18, 0, 0, tzinfo=UTC)


def _config() -> dict[str, object]:
loaded = yaml.safe_load(CONFIG.read_text())
assert isinstance(loaded, dict)
return loaded


def test_config_enforces_the_five_classes_on_added_lines() -> None:
config = _config()
assert config.get("enforce_scope") == "added-lines"
classes = config.get("enforce_classes")
assert isinstance(classes, list)
assert set(classes) == FIVE_CLASSES
assert len(classes) == len(FIVE_CLASSES)


def test_caller_workflow_is_pinned_to_the_added_lines_revision() -> None:
workflow = yaml.safe_load(WORKFLOW.read_text())
uses = workflow["jobs"]["public-repo-hygiene"]["uses"]
assert uses == f"{REUSABLE}@{GATE_REV}"
assert PREVIOUS_REV not in WORKFLOW.read_text()
assert workflow["jobs"]["public-repo-hygiene"]["secrets"] == "inherit"


def _hygiene_hook_blocks() -> list[dict[str, object]]:
precommit = yaml.safe_load(PRECOMMIT.read_text())
blocks = []
for repo in precommit["repos"]:
for hook in repo.get("hooks", []):
if hook.get("id") == "public-repo-hygiene":
blocks.append({"repo": repo, "hook": hook})
return blocks


def test_precommit_runs_the_gate_hook_at_the_same_revision() -> None:
blocks = _hygiene_hook_blocks()
assert len(blocks) == 1, "exactly one public-repo-hygiene hook is declared"
repo = blocks[0]["repo"]
hook = blocks[0]["hook"]
assert isinstance(repo, dict) and isinstance(hook, dict)
assert repo["repo"] == "https://github.com/OmniNode-ai/omniclaude"
assert repo["rev"] == GATE_REV
# The exported hook already runs over the staged added lines (--diff-staged),
# always_run, in the pre-commit stage. An override here would narrow it.
narrowing = {"entry", "args", "files", "exclude", "stages", "always_run"}
assert not narrowing & set(hook), sorted(narrowing & set(hook))


def test_hygiene_context_is_not_excluded_from_the_ci_summary_sweep() -> None:
"""A red gate must fail the CI Summary umbrella: no exclusion admits it."""
assert not [k for k in EXTERNAL_SWEEP_EXCLUSIONS if "hygiene" in k.lower()]


def _row(conclusion: str) -> dict[str, object]:
return {
"id": 20782,
"name": HYGIENE_CONTEXT,
"status": "completed",
"conclusion": conclusion,
"started_at": "2026-10-09T17:00:00Z",
"completed_at": "2026-10-09T17:05:00Z",
"head_sha": "a" * 40,
}


def _sweep(conclusion: str) -> list[str]:
failures, _in_flight, swept, _excluded, _provisional = evaluate_external_sweep(
[_row(conclusion)],
expected=(),
in_run_names=frozenset(),
self_name="CI Summary",
exclusions=EXTERNAL_SWEEP_EXCLUSIONS,
events={},
now=NOW,
)
assert swept == [HYGIENE_CONTEXT], "the sweep must have judged the row"
return failures


def test_a_red_hygiene_gate_fails_the_ci_summary_sweep_and_green_passes() -> None:
assert _sweep("failure") == [f"{HYGIENE_CONTEXT} (failure)"]
assert _sweep("success") == []


@pytest.mark.live_contact(
"tests/ci/fixtures/public_repo_hygiene_added_lines_omn20782.json"
)
def test_gate_at_the_pin_fails_a_planted_lab_config_literal(
recorded_response: dict[str, object],
) -> None:
"""The recorded run of the real gate, at the revision both callers use."""
recording = cast("dict[str, object]", recorded_response["response"])
assert recording["gate_rev"] == GATE_REV
assert recording["exit_code"] == 1
assert "1 enforced finding(s) on lines this change adds" in cast(
"str", recording["enforce_scope_line"]
)
assert [
f.rsplit(": ", 1)[-1] for f in cast("list[str]", recording["added_findings"])
] == ["lab-config"]
assert recording["gate_rev"] in WORKFLOW.read_text()
assert recording["gate_rev"] in PRECOMMIT.read_text()
Loading