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
71 changes: 67 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,9 @@ jobs:
#
# Excludes: uv.lock (dependency hashes), .venv/, test fixtures,
# .git/, .secrets.baseline (contains hashed values that self-trigger),
# workflow YAML (references "secret" in config context)
# workflow YAML (references "secret" in config context),
# url_authority_baseline.json (sha256 fingerprints of known violations,
# not secrets — Hex High Entropy false-positive)
set +e
git ls-files -z | xargs -0 detect-secrets-hook \
--baseline .secrets.baseline \
Expand All @@ -751,7 +753,8 @@ jobs:
--exclude-files 'tests/fixtures/' \
--exclude-files '\.git/' \
--exclude-files '\.secrets\.baseline' \
--exclude-files '\.github/workflows/.*\.yml'
--exclude-files '\.github/workflows/.*\.yml' \
--exclude-files 'url_authority_baseline\.json'
HOOK_EXIT=$?
set -e

Expand Down Expand Up @@ -939,6 +942,63 @@ jobs:
echo "- No orphan producers or consumers" >> $GITHUB_STEP_SUMMARY
echo "- Every widget topic has a producing node" >> $GITHUB_STEP_SUMMARY

# Phase 1 validation job - runs in parallel with all other Phase 1 jobs
# Dispatch-surface real-dispatch-test gate (OMN-12960)
dispatch-surface-test-required:
name: Dispatch-Surface Test Required (OMN-12960)
# OMNI_RUNNER_SELECTOR_V1 - trusted events default to self-hosted omnibase-ci; pull_request defaults to ubuntu-latest
runs-on: >-
${{
github.event_name != 'pull_request'
&& fromJSON(vars.OMNI_TRUSTED_CI_RUNS_ON_JSON || '["self-hosted","omnibase-ci"]')
|| fromJSON(vars.OMNI_PUBLIC_PR_RUNS_ON_JSON || '["ubuntu-latest"]')
}}
timeout-minutes: 5

steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install uv
uses: astral-sh/setup-uv@v7
with:
version: ${{ env.UV_VERSION }}
enable-cache: true
cache-dependency-glob: "uv.lock"

- name: Install dependencies
run: uv sync --all-extras

- name: Resolve base ref
id: base
run: |
if [[ "${{ github.event_name }}" == "pull_request" ]]; then
git fetch --no-tags origin "${{ github.base_ref }}"
echo "ref=origin/${{ github.base_ref }}" >> "$GITHUB_OUTPUT"
else
echo "ref=${{ github.event.before }}" >> "$GITHUB_OUTPUT"
fi

- name: Run dispatch-surface-test-required validator
run: |
uv run python -m omnibase_core.validators.dispatch_surface_test_required \
--base "${{ steps.base.outputs.ref }}"

- name: Dispatch-surface gate summary
if: always()
run: |
echo "## Dispatch-Surface Test Required (OMN-12960)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "PRs touching dispatch / routing / handler-registration / message-envelope source surfaces" >> $GITHUB_STEP_SUMMARY
echo "must include a real-dispatch-path test. Handler-isolation tests pass while live dispatch fails." >> $GITHUB_STEP_SUMMARY

# ---------------------------------------------------------------------------
# Phase 1.5 - Quality Gate (aggregates all Phase 1 quality checks)
# ---------------------------------------------------------------------------
Expand All @@ -954,7 +1014,7 @@ jobs:
&& fromJSON(vars.OMNI_TRUSTED_CI_RUNS_ON_JSON || '["self-hosted","omnibase-ci"]')
|| fromJSON(vars.OMNI_PUBLIC_PR_RUNS_ON_JSON || '["ubuntu-latest"]')
}}
needs: [lint, pyright, exports-validation, mypy-validation-scripts, core-infra-boundary, check-deterministic-skills, docs-validation, node-purity-check, enum-governance, detect-secrets, version-pin-check, sdk-boundary-check, contract-compliance, contract-config-compliance, breaking-schema-change, no-env-fallbacks, demo-path-topic-coherence]
needs: [lint, pyright, exports-validation, mypy-validation-scripts, core-infra-boundary, check-deterministic-skills, docs-validation, node-purity-check, enum-governance, detect-secrets, version-pin-check, sdk-boundary-check, contract-compliance, contract-config-compliance, breaking-schema-change, no-env-fallbacks, demo-path-topic-coherence, dispatch-surface-test-required]
if: always()

steps:
Expand All @@ -978,6 +1038,7 @@ jobs:
breaking_schema="${{ needs.breaking-schema-change.result }}"
no_env_fallbacks="${{ needs.no-env-fallbacks.result }}"
demo_path_gate="${{ needs.demo-path-topic-coherence.result }}"
dispatch_surface="${{ needs.dispatch-surface-test-required.result }}"

# Report each check
echo "| Check | Result |" >> $GITHUB_STEP_SUMMARY
Expand All @@ -997,6 +1058,7 @@ jobs:
echo "| Breaking-Schema-Change (OMN-12621) | $breaking_schema |" >> $GITHUB_STEP_SUMMARY
echo "| No Env Fallbacks (OMN-7227) | $no_env_fallbacks |" >> $GITHUB_STEP_SUMMARY
echo "| Demo-Path Topic Coherence (OMN-12777) | $demo_path_gate |" >> $GITHUB_STEP_SUMMARY
echo "| Dispatch-Surface Test Required (OMN-12960) | $dispatch_surface |" >> $GITHUB_STEP_SUMMARY

# Gate logic: all must pass
if [[ "$lint" == "success" ]] && \
Expand All @@ -1013,7 +1075,8 @@ jobs:
[[ "$contract_config" == "success" ]] && \
[[ "$breaking_schema" == "success" ]] && \
[[ "$no_env_fallbacks" == "success" ]] && \
[[ "$demo_path_gate" == "success" ]]; then
[[ "$demo_path_gate" == "success" ]] && \
[[ "$dispatch_surface" == "success" ]]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Quality Gate: PASSED**" >> $GITHUB_STEP_SUMMARY
echo "All quality checks passed."
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/main-target-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# SPDX-License-Identifier: MIT
#
# OMN-12243: Block PRs targeting main unless they are promotion PRs
# from dev with a promotion receipt.
# OMN-12477: Removed the hotfix/* bypass — main accepts only dev promotions.
# from dev or promotion/* with a promotion receipt.
# OMN-12477: Removed the hotfix/* bypass — main accepts only promotions.

name: Main target guard

Expand Down Expand Up @@ -38,19 +38,19 @@ jobs:

echo "PR targets main. Checking head ref: '${HEAD_REF}'"

if [[ "${HEAD_REF}" == "dev" ]]; then
if [[ "${HEAD_REF}" == "dev" || "${HEAD_REF}" == promotion/* ]]; then
if echo "${PR_BODY}" | grep -qE 'promotion-receipt:[[:space:]]*OCC-[0-9]+'; then
echo "::notice::Promotion PR from dev with valid promotion-receipt. Allowed."
echo "::notice::Promotion PR from '${HEAD_REF}' with valid promotion-receipt. Allowed."
exit 0
fi

echo "::error::PR is from dev but is missing a promotion receipt."
echo "::error::Promotion PR from '${HEAD_REF}' is missing a promotion receipt."
echo "::error::Add a line matching: promotion-receipt: OCC-<number>"
echo "::error::Retarget this PR to dev; main accepts only promotion PRs."
exit 1
fi

echo "::error::PRs targeting main must come from dev."
echo "::error::PRs targeting main must come from dev or promotion/*."
echo "::error::Head ref '${HEAD_REF}' is not permitted to target main directly."
echo "::error::Retarget this PR to dev; main accepts only promotion PRs."
exit 1
113 changes: 113 additions & 0 deletions .github/workflows/url-authority-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# SPDX-FileCopyrightText: 2026 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
#
# OMN-12818 (PR-2 of OMN-12803): url-authority ratchet gate — BLOCKING MODE.
#
# Every URL/endpoint must resolve from a contract (the routing authority for
# model endpoints, the integration catalog for external/infra endpoints) — never
# from a public-HTTPS source literal or a *_URL / *_ENDPOINT env read. API-key
# env reads and config-PATH reads stay legal.
#
# This is a RATCHET: existing violations are grandfathered by the sha256 baseline
# at src/omnibase_core/validation/baselines/url_authority_baseline.json.
# Any NEW violation (fingerprint absent from the baseline) fails the job and blocks
# merge. The baseline is burn-down only — a CI assertion rejects baseline growth.
#
# To permit a legitimate literal, annotate the line:
# # url-authority-ok: <concrete reason>
#
# Migration debt tickets:
# omnibase_core: OMN-12806
# omnibase_infra: OMN-12807
# other repos: OMN-12808
#
# This job is a REQUIRED STATUS CHECK. The job name "URL Authority Gate" must
# match branch protection rules.

name: URL Authority Gate

on:
pull_request:
types: [opened, edited, synchronize, reopened]
branches: [main, dev]
merge_group:

permissions:
contents: read

jobs:
url-authority-gate:
name: URL Authority Gate
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up uv
uses: astral-sh/setup-uv@v5

- name: Install dependencies
run: uv sync --all-extras

- name: Run url-authority gate (full-repo, against frozen baseline)
run: |
uv run python -m omnibase_core.validation.validator_url_authority \
--all --repo omnibase_core --repo-root .

- name: Assert baseline did not grow (anti-gaming)
run: |
uv run python - <<'PY'
import json
import subprocess
import sys
from pathlib import Path

baseline = Path(
"src/omnibase_core/validation/baselines/url_authority_baseline.json"
)
head = json.loads(baseline.read_text())
head_repo = {
e["fingerprint"]
for e in head["violations"]
if e["repo"] == "omnibase_core"
}
# Compare against the baseline on the PR base branch.
base_ref = "origin/${{ github.base_ref || 'dev' }}"
try:
prior_text = subprocess.check_output(
["git", "show", f"{base_ref}:{baseline}"], text=True
)
prior = json.loads(prior_text)
prior_repo = {
e["fingerprint"]
for e in prior["violations"]
if e["repo"] == "omnibase_core"
}
except subprocess.CalledProcessError:
# Baseline file does not exist on the base branch — this is the
# initial introduction of the baseline for this repo. By definition
# nothing "grew" relative to a non-existent prior, so the check
# passes. Any new violations that sneak in via the first commit are
# caught by the gate step above.
print(
"URL-AUTHORITY BASELINE OK (initial): "
f"baseline file is new on this PR ({len(head_repo)} grandfathered "
"fingerprints); no prior to compare against."
)
sys.exit(0)
grew = head_repo - prior_repo
if grew:
sys.stderr.write(
"URL-AUTHORITY BASELINE GREW: "
f"{len(grew)} new fingerprint(s) added to the baseline. The baseline "
"is burn-down only — fix the violation or annotate with "
"# url-authority-ok:, never grow the baseline.\n"
)
sys.exit(1)
print(
"URL-AUTHORITY BASELINE OK: "
f"omnibase_core subset {len(head_repo)} (<= prior {len(prior_repo)})."
)
PY
31 changes: 30 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ repos:
language: system
pass_filenames: true
files: ^.*\.py$
exclude: ^(scripts/validation/validate-contracts\.py|scripts/validation/validate-string-versions\.py|scripts/validation/check_stub_implementations\.py|scripts/validation/validate-no-manual-yaml\.py|scripts/validation/validate_pr_deploy_required\.py|scripts/compute_contract_fingerprint\.py|scripts/lint_contract\.py|src/omnibase_core/utils/safe_yaml_loader\.py|src/omnibase_core/utils/util_safe_yaml_loader\.py|src/omnibase_core/validation/contracts\.py|src/omnibase_core/validation/contract_validator\.py|src/omnibase_core/validation/validator_base\.py|src/omnibase_core/validation/validator_any_type\.py|src/omnibase_core/validation/validator_contract_linter\.py|src/omnibase_core/validation/validator_requirements_consumer\.py|src/omnibase_core/validation/validator_runtime_profiles\.py|src/omnibase_core/discovery/mixin_discovery\.py|src/omnibase_core/cli/cli_demo\.py|tests/fixtures/validation/|src/omnibase_core/validation/scripts/validate_string_versions\.py|src/omnibase_core/validators/contract_config_compliance\.py|src/omnibase_core/validators/gitignore_baseline\.py)
exclude: ^(scripts/validation/validate-contracts\.py|scripts/validation/validate-string-versions\.py|scripts/validation/check_stub_implementations\.py|scripts/validation/validate-no-manual-yaml\.py|scripts/validation/validate_pr_deploy_required\.py|scripts/compute_contract_fingerprint\.py|scripts/lint_contract\.py|src/omnibase_core/utils/safe_yaml_loader\.py|src/omnibase_core/utils/util_safe_yaml_loader\.py|src/omnibase_core/validation/contracts\.py|src/omnibase_core/validation/contract_validator\.py|src/omnibase_core/validation/validator_base\.py|src/omnibase_core/validation/validator_any_type\.py|src/omnibase_core/validation/validator_contract_linter\.py|src/omnibase_core/validation/validator_requirements_consumer\.py|src/omnibase_core/validation/validator_runtime_profiles\.py|src/omnibase_core/discovery/mixin_discovery\.py|src/omnibase_core/cli/cli_demo\.py|tests/fixtures/validation/|src/omnibase_core/validation/scripts/validate_string_versions\.py|src/omnibase_core/validators/contract_config_compliance\.py|src/omnibase_core/validators/gitignore_baseline\.py|src/omnibase_core/validators/skill_dispatch_receipt_mode\.py)
stages: [pre-commit]
# validate_string_versions.py: validation script that must load YAML to validate it (OMN-4402)

Expand Down Expand Up @@ -539,6 +539,21 @@ repos:
always_run: true
stages: [pre-commit]

# Dispatch-surface real-dispatch-test gate (OMN-12960)
# Requires a real-dispatch-path test for any commit touching dispatch,
# routing, handler-registration, or message-envelope source surfaces.
# Handler-isolation tests pass while live dispatch fails — the only durable
# guard is a test that drives the real dispatcher registration.
# Suppress a genuinely test-irrelevant change with:
# # dispatch-surface-test-ok: <reason>
- id: dispatch-surface-test-required
name: Dispatch-Surface Test Required (OMN-12960)
entry: env -u PYTHONPATH uv run python -m omnibase_core.validators.dispatch_surface_test_required
language: system
pass_filenames: true
files: ^(src|tests)/.*\.py$
stages: [pre-commit]

# Breaking-schema-change compliance (OMN-12621)
# Requires an adjacent *.migration.yaml ModelTopicMigrationContract whenever
# a *.topic-schema.yaml declares a breaking topic-schema delta (major_bump
Expand Down Expand Up @@ -808,6 +823,20 @@ repos:
pass_filenames: true
stages: [pre-commit]

# URL Authority Gate (OMN-12818, PR-2 of OMN-12803).
# Ratchet: NEW fingerprints fail; existing violations are grandfathered.
# Baseline is burn-down only. Migration debt: OMN-12806 (omnibase_core).
# Suppress with: # url-authority-ok: <reason>
- id: check-url-authority
name: URL Authority Gate (OMN-12818)
entry: uv run python -m omnibase_core.validation.validator_url_authority
args: [--repo, omnibase_core, --repo-root, .]
language: system
types: [python]
pass_filenames: true
exclude: ^(tests/|archived/|archive/).*\.py$
stages: [pre-commit]

- id: validate-exception-handling
name: ONEX Exception Handling Validation
entry: uv run python scripts/validation/validate-exception-handling.py
Expand Down
35 changes: 32 additions & 3 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@
description: >
Scan ModelDodReceipt YAML files for gaming patterns: no-op echo probes marked PASS, deferral language
(PENDING/TBD/TODO) in PASS receipts, self-attestation (verifier==runner), fake human verifiers (agent
runner + human verifier name), and deploy-keyword evidence backed only by echo probe_command. Fails on
any violation in supplied receipt files. Operates over onex_change_control/drift/dod_receipts/ when no
filenames are supplied; pass --receipts-dir to override.
runner + human verifier name), and deploy-keyword evidence backed only by echo probe_command. Fails
on any violation in supplied receipt files. Operates over onex_change_control/drift/dod_receipts/
when no filenames are supplied; pass --receipts-dir to override.
language: python
entry: python -m omnibase_core.validation.validator_receipt_honesty
types: [yaml]
Expand All @@ -133,3 +133,32 @@
types: [python]
pass_filenames: true
stages: [pre-commit]

- id: transport-mock-lint
name: Transport Mock Lint (OMN-13026)
description: >
Detect bare AsyncMock()/MagicMock() without spec=/spec_set= on EventBus/transport/Kafka mock surfaces
in test files. Reference incident: PR #1181 bare AsyncMock hid missing EventBusKafka.stop(), masking
a runtime-fatal bug. Pass --baseline <yaml> to activate ratchet mode (new violations blocked; existing
ones tracked per-site). Suppression: # transport-mock-ok: <reason> on the offending line.
language: python
entry: python -m omnibase_core.validators.transport_mock_lint
types: [python]
files: (^|/)test_.*\.py$
pass_filenames: true
stages: [pre-commit]

- id: check-url-authority
name: URL Authority Gate (OMN-12818)
description: >
Ratchet gate: reject NEW URL/endpoint literals outside contracts (PR-2 of OMN-12803). Every URL and
*_URL/*_ENDPOINT env read must resolve from a contract (routing authority / integration catalog).
Existing violations are grandfathered by sha256 fingerprint; only NEW fingerprints fail the gate.
Baseline is burn-down only — may shrink, never grow. Migration debt: OMN-12806 (omnibase_core), OMN-12807
(omnibase_infra), OMN-12808 (others). Suppression: # url-authority-ok: <reason>
language: python
entry: python -m omnibase_core.validation.validator_url_authority
args: [--repo, omnibase_core, --repo-root, .]
types: [python]
pass_filenames: true
stages: [pre-commit]
8 changes: 4 additions & 4 deletions .yaml-validation-allowlist.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,10 @@ allowed_files:
- file: "src/omnibase_core/validators/breaking_schema_change.py"
reason: >-
Breaking-schema-change validator (OMN-12621) — must read adjacent *.topic-schema.yaml declarations
and *.migration.yaml contracts from the filesystem to diff baseline vs current bindings. Every
yaml.safe_load() result is immediately passed to ModelTopicSchemaBinding.model_validate() /
ModelTopicMigrationContract.model_validate(); the raw dict is never used unvalidated. Mirrors the
contract_config_compliance.py validator pattern it is modeled on.
and *.migration.yaml contracts from the filesystem to diff baseline vs current bindings. Every yaml.safe_load()
result is immediately passed to ModelTopicSchemaBinding.model_validate() / ModelTopicMigrationContract.model_validate();
the raw dict is never used unvalidated. Mirrors the contract_config_compliance.py validator pattern
it is modeled on.
added: "2026-06-02"
review: "2026-12-13"

Expand Down
Loading
Loading