Skip to content

ci: add recursive pass validation gates#114

Open
cryptoxdog wants to merge 1 commit into
mainfrom
ci/recursive-pass-gates
Open

ci: add recursive pass validation gates#114
cryptoxdog wants to merge 1 commit into
mainfrom
ci/recursive-pass-gates

Conversation

@cryptoxdog

@cryptoxdog cryptoxdog commented May 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds recursive pass CI validation gates and schema validation support.

Files

  • ci/validate_schema.py
  • github/workflows/recursive-pass-ci.yml

Notes

The workflow file is currently placed at github/workflows/recursive-pass-ci.yml (without the leading dot) due to GitHub App token permission restrictions on .github/workflows/.

Fix when merging: Rename github/workflows/recursive-pass-ci.yml.github/workflows/recursive-pass-ci.yml so GitHub Actions can discover it.

If this PR needs to be undone during merge/review, revert the workflow placement.

Verification

  • Files uploaded from provided pack.
  • No content refactors or formatting changes applied.

Summary by CodeRabbit

  • Chores
    • Added validation tooling for specification artifacts
    • Introduced multi-stage automated checking workflow with compliance verification
    • Enabled artifact collection and evidence reporting for CI pipeline

Copy link
Copy Markdown
Collaborator Author

Workflow file was intentionally placed at github/workflows/recursive-pass-ci.yml (without the leading dot) to bypass GitHub App token permission restrictions.

Action required at merge: Move github/workflows/recursive-pass-ci.yml.github/workflows/recursive-pass-ci.yml so GitHub Actions can discover it. Fix when merging.

If this PR needs to be reverted during merge/review, undo that path placement.

@github-actions

github-actions Bot commented May 6, 2026

Copy link
Copy Markdown

PR Too Large
Lines changed: 1049
Limit: 1000 lines
Action Required: Break into smaller, atomic PRs

📋 Best Practices for Large Changes

  1. Refactoring + Features: Separate into 2 PRs
  2. Multiple Features: One PR per feature
  3. Database + Code: Separate migration from logic
  4. Generated Code: Separate from manual changes

🚫 This PR is blocked from merging until size limits are met.

@coderabbitai

coderabbitai Bot commented May 6, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

A comprehensive schema validation system for GMP-RSP-001 artifacts is introduced. A Python utility script provides multiple validation modes (YAML sections, completeness, halt codes, schema versions, quality logs, drift detection), while a GitHub Actions workflow orchestrates multi-pass CI gates that validate spec completeness, section presence, and cross-file consistency across recursive-pass YAML files, culminating in pytest and artifact collection.

Changes

Schema Validation Framework & CI Orchestration

Layer / File(s) Summary
Validation Utilities
ci/validate_schema.py
New Python CLI tool implementing YAML loading, nested key traversal, section validation, completeness checks, halt-code verification, semver parsing, quality log validation, and drift detection. Includes 13 public functions and exit-code error handling for GMP-RSP-001 artifact validation.
CI Workflow & Multi-Pass Gating
github/workflows/recursive-pass-ci.yml
New GitHub Actions workflow implementing a preflight environment check, three consecutive validation passes (pass-0, pass-1, pass-2) with schema completeness and section presence checks, drift checks between passes, schema version parity validation, pytest execution, and ci_evidence_report artifact generation. Invokes validate_schema.py and embedded Python snippets to gate spec compliance.

Sequence Diagram

sequenceDiagram
    participant GH as GitHub
    participant WF as Workflow
    participant Validate as validate_schema.py
    participant Specs as YAML Specs
    participant Tests as pytest
    participant Artifacts as Artifacts

    GH->>WF: Trigger on push/PR/dispatch
    WF->>WF: Preflight: Verify files exist
    WF->>Validate: validate_schema check_completeness (pass-0)
    Validate->>Specs: Load recursive-pass-0.yaml
    Specs-->>Validate: YAML data
    Validate->>Validate: Check required sections
    Validate-->>WF: PASS or FAIL
    WF->>Validate: validate_schema check_completeness (pass-1)
    Validate->>Specs: Load recursive-pass-1.yaml
    Specs-->>Validate: YAML data
    Validate-->>WF: PASS or FAIL
    WF->>Validate: validate_schema check_completeness (pass-2)
    Validate->>Specs: Load recursive-pass-2.yaml
    Specs-->>Validate: YAML data
    Validate-->>WF: PASS or FAIL
    WF->>Validate: validate_schema check_pass_chain
    Validate->>Specs: Load all pass YAMLs
    Specs-->>Validate: YAML data
    Validate->>Validate: Verify schema_version consistency
    Validate-->>WF: Chain integrity result
    WF->>Validate: validate_schema drift_check (pass-0 ↔ pass-1)
    Validate->>Specs: Load both YAMLs
    Specs-->>Validate: YAML data
    Validate->>Validate: Compare contract keys
    Validate-->>WF: Drift report
    WF->>Tests: pytest execution
    Tests-->>WF: Test results & exit code
    WF->>Artifacts: Upload pytest results
    WF->>WF: Collect CI evidence
    WF->>Artifacts: Upload ci_evidence_report.json
    Artifacts-->>GH: Evidence stored
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Hops through YAMLs deep and wide,
Schemas validated, specs applied,
Pass-by-pass the gates align,
Drift-checked, versioned—all divine!
GMP artifacts now certified,
With CI gates the future's tried!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci: add recursive pass validation gates' directly and clearly describes the main change: adding CI validation gates for the recursive pass system.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/recursive-pass-gates

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud

sonarqubecloud Bot commented May 6, 2026

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 39f41a657e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -0,0 +1,625 @@
# ci/workflows/recursive-pass-ci.yml

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Place workflow under .github/workflows

This workflow is added under github/workflows/ instead of .github/workflows/, so GitHub Actions will not discover or run it on push, pull_request, or workflow_dispatch. In that state, all validation gates introduced here are silently bypassed and cannot protect main.

Useful? React with 👍 / 👎.

Comment on lines +97 to +101
"${{ env.CI_DIR }}/required_sections_pass0.txt" \
"${{ env.CI_DIR }}/shared_contracts.yaml" \
"${{ env.CI_DIR }}/halt_codes_pass0.txt" \
"${{ env.SPECS_DIR }}/recursive-pass-0.yaml" \
"${{ env.SPECS_DIR }}/recursive-pass-1.yaml" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove hard-fail checks for files absent in this repo

The preflight step requires artifacts like ci/required_sections_pass0.txt, specs/recursive-pass-0.yaml, and docs/pass_quality_log.yaml, but those files are not present in this commit; once this workflow is moved to .github/workflows/, preflight will fail immediately on every run and block all PRs. The gate should either include these artifacts in the same change or guard the checks behind an onboarding mode.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🧹 Nitpick comments (3)
ci/validate_schema.py (1)

374-424: ⚡ Quick win

Consider replacing the hand-rolled CLI dispatch with argparse.

SonarCloud flags cognitive complexity at 23 (limit 15) for main, driven by repeated len(args) checks and string-mode comparisons. argparse subparsers (or even a dict[str, Callable] lookup) would also give you --help for free, which the workflow's preflight step at line 90 currently tries to exercise but only succeeds because of the trailing || true.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/validate_schema.py` around lines 374 - 424, The main function currently
does manual argv parsing and many len(args)/mode checks; replace this with
argparse subparsers (or a dict[str, Callable] dispatcher) to reduce cognitive
complexity: create an ArgumentParser in main with subparsers for the modes and
required positional arguments for each subcommand, map subcommands to the
existing handlers (check_section, check_quality_log, check_completeness,
check_halt_codes, check_pass_chain, check_schema_version, drift_check), call the
appropriate handler with parsed args, and use parser.error or _fail for uniform
error exits so you can remove the repeated len(args) checks and manual error
strings while preserving exit behavior.
github/workflows/recursive-pass-ci.yml (2)

38-46: 💤 Low value

warn_only input is declared but never wired in.

The footer at lines 621–624 admits warn_only "does not suppress gate failures in v2 — it is provided as a documentation signal only". Operators dispatching the workflow with warn_only: true will reasonably expect non-blocking behavior and be surprised when gates still fail the run. Either drop the input or actually use it — for example by gating each blocking step on if: inputs.warn_only != 'true', or wrapping with continue-on-error: ${{ inputs.warn_only == 'true' }}.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/workflows/recursive-pass-ci.yml` around lines 38 - 46, The workflow
input warn_only is declared but not used; either remove it or wire it into the
blocking steps so dispatching with warn_only: "true" produces non-blocking
behavior. Fix by locating the workflow_dispatch inputs block (warn_only) and
then for each gate/blocking job or step that should be non-blocking when warned,
add one of two options: 1) conditionally skip the blocking behavior with if: ${{
inputs.warn_only != 'true' }} on the blocking step/job (so it only runs when
warn_only is false), or 2) allow failures by adding continue-on-error: ${{
inputs.warn_only == 'true' }} to those steps; apply consistently to all gate
steps referenced in the workflow so the input actually toggles blocking vs
warn-only behavior.

113-347: ⚖️ Poor tradeoff

Optional: factor the repeated checkout/setup/pip block into a composite action.

Every gate job repeats the same five-step preamble (actions/checkout@v4actions/setup-python@v5pip install pyyaml). A composite action under .github/actions/setup-validator/action.yml (or a reusable workflow) would reduce ~70 lines of duplication and eliminate the risk that one job drifts (e.g. forgets cache: pip or installs a different dep set). Not blocking — file is functional as-is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/workflows/recursive-pass-ci.yml` around lines 113 - 347, The CI file
repeats the same checkout/setup-python/pip install pyyaml preamble across jobs
like schema-completeness-pass0, schema-completeness-pass1,
schema-completeness-pass2, observability-section-present,
principal-hierarchy-present, etc.; create a composite action (e.g.
.github/actions/setup-validator/action.yml) that performs actions/checkout@v4,
actions/setup-python@v5 with a python-version input and pip cache, and runs pip
install pyyaml, then update each job to replace the repeated steps with a single
step using your composite action (pass through inputs like python-version and
any env vars) so each job calls the composite action instead of duplicating the
three steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ci/validate_schema.py`:
- Around line 303-311: When validating entries, first ensure each entry is a
mapping before checking required fields: in the loop that iterates over entries
(the variable entries and QUALITY_LOG_FIELDS), add a type guard such as "if not
isinstance(entry, Mapping): errors.append(f'entry[{i}] must be a mapping
(CONTRACT_INTEGRITY_VIOLATION)'); continue" so non-dict entries fail loudly
instead of performing membership checks; otherwise proceed to check missing
fields as before. Also remove the redundant "FAIL:" prefix from the message
passed into _fail (call site currently embeds "FAIL:"), leaving only the
descriptive message so the final output doesn't repeat "FAIL:".
- Around line 128-140: The list-comprehension opens sidecar files without
closing them; replace direct open(...) usage with a safe read pattern — e.g.,
use Path(required_file).read_text().splitlines() (or wrap with with
open(required_file) as f: f.readlines()) when building `required`, and apply the
same fix for `halt_codes_file` in `check_halt_codes` and `contract_keys_file` in
`drift_check` so those file handles are not leaked; update the comprehension
that builds `required` and the analogous comprehensions in the
`check_halt_codes` and `drift_check` code paths to use the chosen safe-read
approach.
- Around line 44-108: Add explicit type annotations to every function signature
in this module: annotate parameters and return types for load_yaml, _fail,
is_non_empty, get_nested (and also for the other functions referenced in the
review: check_section, check_completeness, check_halt_codes, check_pass_chain,
_parse_semver, check_schema_version, check_quality_log, drift_check, main) using
appropriate types (e.g., path: str -> dict | None for loaders, msg: str for
_fail, v: Any -> bool for is_non_empty, data: Mapping[str, Any], keys:
Sequence[str] -> Any | None for get_nested, etc.), and add
Any/Mapping/Sequence/Optional imports as needed; then run ruff auto-fixes (ruff
format / ruff check --fix) to sort/format imports to resolve I001. Ensure
mypy/strict passes after adding the annotations.
- Around line 62-75: The _fail function should be annotated with typing.NoReturn
so callers like load_yaml are inferred as non-returning; add "from typing import
NoReturn" (or update existing typing imports) and change the signature of _fail
to include "-> NoReturn" (keep parameters _fail(msg, prefix="ERROR") ->
NoReturn) so mypy --strict no longer treats exception branches as returning
None.

In `@github/workflows/recursive-pass-ci.yml`:
- Around line 84-91: The CI step currently runs python ${{ env.VALIDATE_SCRIPT
}} --help which is a no-op and masked by the trailing || true; either add a
proper --help handler to validate_schema.py (e.g. use argparse and implement a
--help/--mode docstring around the main entry referenced near
validate_schema.py:374) so the script returns 0 for --help, or remove the flaky
smoke test and replace it with a robust module-import smoke test that executes
the file without masking errors (use importlib.util.spec_from_file_location +
spec.loader.exec_module to load ${{ env.VALIDATE_SCRIPT }}), and ensure you drop
the trailing || true so real import/Syntax errors fail the workflow.
- Around line 1-2: The workflow file named recursive-pass-ci.yml is currently
placed outside GitHub's auto-discovery directory; move the file into the
repository's workflows directory used by GitHub Actions (so Actions will pick up
the workflow), update the top-of-file comment that currently reads "#
ci/workflows/recursive-pass-ci.yml" to reflect the new location, and update the
SETUP reference mentioned around the file's SETUP section (line referenced in
the review) so it points to the new location; after committing the move, verify
the workflow appears and runs in the Actions UI and that branch-protection jobs
(e.g., "Full test suite — tests/test_pass_invariants.py") resolve correctly.
- Around line 51-53: Update the CI environment variable PYTHON_VERSION from
"3.11" to "3.12" in the workflow env block so the jobs run under the project's
Python baseline; keep VALIDATE_SCRIPT unchanged. Locate the env section
containing PYTHON_VERSION and replace its value to "3.12" (or higher if desired)
so mypy/validate_schema.py and other lint/type checks execute with Python 3.12
semantics.
- Around line 514-542: The inline Python step uses datetime.datetime.utcnow()
(deprecated/naive), a bare except, and open(path) without a context manager;
update imports to include timezone (or use datetime.now(tz=timezone.utc)) and
replace datetime.datetime.utcnow().isoformat() with a timezone-aware call (e.g.,
datetime.now(timezone.utc).isoformat() + "Z"); open each spec file with a
context manager (with open(path) as f: data = yaml.safe_load(f)); tighten error
handling by catching specific exceptions (e.g., yaml.YAMLError and OSError)
instead of bare except Exception as e and record their messages into
report["specs"][name].

---

Nitpick comments:
In `@ci/validate_schema.py`:
- Around line 374-424: The main function currently does manual argv parsing and
many len(args)/mode checks; replace this with argparse subparsers (or a
dict[str, Callable] dispatcher) to reduce cognitive complexity: create an
ArgumentParser in main with subparsers for the modes and required positional
arguments for each subcommand, map subcommands to the existing handlers
(check_section, check_quality_log, check_completeness, check_halt_codes,
check_pass_chain, check_schema_version, drift_check), call the appropriate
handler with parsed args, and use parser.error or _fail for uniform error exits
so you can remove the repeated len(args) checks and manual error strings while
preserving exit behavior.

In `@github/workflows/recursive-pass-ci.yml`:
- Around line 38-46: The workflow input warn_only is declared but not used;
either remove it or wire it into the blocking steps so dispatching with
warn_only: "true" produces non-blocking behavior. Fix by locating the
workflow_dispatch inputs block (warn_only) and then for each gate/blocking job
or step that should be non-blocking when warned, add one of two options: 1)
conditionally skip the blocking behavior with if: ${{ inputs.warn_only != 'true'
}} on the blocking step/job (so it only runs when warn_only is false), or 2)
allow failures by adding continue-on-error: ${{ inputs.warn_only == 'true' }} to
those steps; apply consistently to all gate steps referenced in the workflow so
the input actually toggles blocking vs warn-only behavior.
- Around line 113-347: The CI file repeats the same checkout/setup-python/pip
install pyyaml preamble across jobs like schema-completeness-pass0,
schema-completeness-pass1, schema-completeness-pass2,
observability-section-present, principal-hierarchy-present, etc.; create a
composite action (e.g. .github/actions/setup-validator/action.yml) that performs
actions/checkout@v4, actions/setup-python@v5 with a python-version input and pip
cache, and runs pip install pyyaml, then update each job to replace the repeated
steps with a single step using your composite action (pass through inputs like
python-version and any env vars) so each job calls the composite action instead
of duplicating the three steps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7ee75dac-8508-4b8a-93c9-0473d9be1f86

📥 Commits

Reviewing files that changed from the base of the PR and between 48ac88b and 39f41a6.

📒 Files selected for processing (2)
  • ci/validate_schema.py
  • github/workflows/recursive-pass-ci.yml

Comment thread ci/validate_schema.py
Comment on lines +44 to +108
import sys
import re
import yaml


# ── Required quality log entry fields ──────────────────────────────────────
QUALITY_LOG_FIELDS = [
"pass_id",
"timestamp_utc",
"gap_ids_addressed",
"gate_results",
"regression_count",
"improvement_delta",
"notes",
]


# ── YAML loading ────────────────────────────────────────────────────────────
def load_yaml(path):
try:
with open(path) as f:
return yaml.safe_load(f)
except FileNotFoundError:
_fail(f"not found: {path}")
except yaml.YAMLError as e:
_fail(f"YAML parse error in {path}: {e}")


def _fail(msg, prefix="ERROR"):
print(f"{prefix}: {msg}", file=sys.stderr)
sys.exit(1)


# ── Value helpers ───────────────────────────────────────────────────────────
def is_non_empty(v):
"""
Return True if v is a non-None, non-empty value.

FIX (v2): Previously, `float` values including 0.0 fell through to the
final `return True` branch, which is correct — 0.0 IS a defined value.
The bug was that `isinstance(v, (str, list, dict))` did NOT match float,
so 0.0 returned True correctly in v1 as well. The actual bug was that
integer 0 and boolean False also returned True (correct), but improvement_delta
of exactly 0.0 in the quality log is a semantically meaningful value
that SHOULD be accepted, not rejected. No change needed for 0.0.

The real fix: ensure None → False, empty str/list/dict → False,
and all other defined values (including 0, 0.0, False) → True.
This is the correct semantics for "field is present and has a value".
"""
if v is None:
return False
if isinstance(v, (str, list, dict)) and len(v) == 0:
return False
return True


def get_nested(data, keys):
"""Traverse nested dict by key list. Returns None if any key is missing."""
node = data
for k in keys:
if not isinstance(node, dict) or k not in node:
return None
node = node[k]
return node

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add type hints to every function signature (coding-guideline violation).

None of the functions in this file (load_yaml, _fail, is_non_empty, get_nested, check_section, check_completeness, check_halt_codes, check_pass_chain, _parse_semver, check_schema_version, check_quality_log, drift_check, main) have parameter or return-type annotations. Combined with mypy --strict engine/, this will block CI/merge. While here, ruff also flags I001 (import block unsorted) — fix with ruff format/ruff check --fix.

♻️ Suggested signatures (illustrative)
-import sys
-import re
-import yaml
+import re
+import sys
+from typing import Any, NoReturn
+
+import yaml
@@
-def load_yaml(path):
+def load_yaml(path: str) -> Any:
@@
-def _fail(msg, prefix="ERROR"):
+def _fail(msg: str, prefix: str = "ERROR") -> NoReturn:
@@
-def is_non_empty(v):
+def is_non_empty(v: Any) -> bool:
@@
-def get_nested(data, keys):
+def get_nested(data: Any, keys: list[str]) -> Any:
@@
-def check_section(section_path, filepath):
+def check_section(section_path: str, filepath: str) -> None:
@@
-def check_completeness(filepath, required_file):
+def check_completeness(filepath: str, required_file: str) -> None:
@@
-def check_halt_codes(filepath, halt_codes_file):
+def check_halt_codes(filepath: str, halt_codes_file: str) -> None:
@@
-def check_pass_chain(filepaths):
+def check_pass_chain(filepaths: list[str]) -> None:
@@
-def _parse_semver(v):
+def _parse_semver(v: object) -> tuple[int, int, int] | None:
@@
-def check_schema_version(filepath, minimum_version):
+def check_schema_version(filepath: str, minimum_version: str) -> None:
@@
-def check_quality_log(filepath):
+def check_quality_log(filepath: str) -> None:
@@
-def drift_check(file_a, file_b, contract_keys_file):
+def drift_check(file_a: str, file_b: str, contract_keys_file: str) -> None:
@@
-def main():
+def main() -> None:

As per coding guidelines: "Type hints on every function signature, class attribute, ambiguous variable" and "Add type hints to all function parameters and return types in Python files".

🧰 Tools
🪛 GitHub Check: Lint & Format (Ruff + MyPy)

[failure] 96-98: ruff (SIM103)
ci/validate_schema.py:96:5: SIM103 Return the negated condition directly
help: Inline condition


[failure] 64-64: ruff (PTH123)
ci/validate_schema.py:64:14: PTH123 open() should be replaced by Path.open()
help: Replace with Path.open()


[failure] 44-46: ruff (I001)
ci/validate_schema.py:44:1: I001 Import block is un-sorted or un-formatted
help: Organize imports

🪛 GitHub Check: Refactoring Validation Gate

[failure] 96-98: ruff (SIM103)
ci/validate_schema.py:96:5: SIM103 Return the negated condition directly
help: Inline condition


[failure] 64-64: ruff (PTH123)
ci/validate_schema.py:64:14: PTH123 open() should be replaced by Path.open()
help: Replace with Path.open()


[failure] 44-46: ruff (I001)
ci/validate_schema.py:44:1: I001 Import block is un-sorted or un-formatted
help: Organize imports

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/validate_schema.py` around lines 44 - 108, Add explicit type annotations
to every function signature in this module: annotate parameters and return types
for load_yaml, _fail, is_non_empty, get_nested (and also for the other functions
referenced in the review: check_section, check_completeness, check_halt_codes,
check_pass_chain, _parse_semver, check_schema_version, check_quality_log,
drift_check, main) using appropriate types (e.g., path: str -> dict | None for
loaders, msg: str for _fail, v: Any -> bool for is_non_empty, data: Mapping[str,
Any], keys: Sequence[str] -> Any | None for get_nested, etc.), and add
Any/Mapping/Sequence/Optional imports as needed; then run ruff auto-fixes (ruff
format / ruff check --fix) to sort/format imports to resolve I001. Ensure
mypy/strict passes after adding the annotations.

Comment thread ci/validate_schema.py
Comment on lines +62 to +75
def load_yaml(path):
try:
with open(path) as f:
return yaml.safe_load(f)
except FileNotFoundError:
_fail(f"not found: {path}")
except yaml.YAMLError as e:
_fail(f"YAML parse error in {path}: {e}")


def _fail(msg, prefix="ERROR"):
print(f"{prefix}: {msg}", file=sys.stderr)
sys.exit(1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Type _fail as NoReturn so callers correctly infer non-return.

_fail always calls sys.exit(1), but without NoReturn, mypy --strict will treat callers like load_yaml as falling through to an implicit None return on the except branches, polluting the inferred return type and propagating Optional everywhere downstream. Annotating _fail removes that noise.

-def _fail(msg, prefix="ERROR"):
+def _fail(msg: str, prefix: str = "ERROR") -> NoReturn:
     print(f"{prefix}: {msg}", file=sys.stderr)
     sys.exit(1)

As per coding guidelines: "Linter: ruff check + mypy --strict engine/".

🧰 Tools
🪛 GitHub Check: Lint & Format (Ruff + MyPy)

[failure] 64-64: ruff (PTH123)
ci/validate_schema.py:64:14: PTH123 open() should be replaced by Path.open()
help: Replace with Path.open()

🪛 GitHub Check: Refactoring Validation Gate

[failure] 64-64: ruff (PTH123)
ci/validate_schema.py:64:14: PTH123 open() should be replaced by Path.open()
help: Replace with Path.open()

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/validate_schema.py` around lines 62 - 75, The _fail function should be
annotated with typing.NoReturn so callers like load_yaml are inferred as
non-returning; add "from typing import NoReturn" (or update existing typing
imports) and change the signature of _fail to include "-> NoReturn" (keep
parameters _fail(msg, prefix="ERROR") -> NoReturn) so mypy --strict no longer
treats exception branches as returning None.

Comment thread ci/validate_schema.py
Comment on lines +128 to +140
try:
required = [
line.strip()
for line in open(required_file)
if line.strip() and not line.startswith("#")
]
except FileNotFoundError:
_fail(f"required sections file not found: {required_file}")

missing = [
s for s in required
if not is_non_empty(get_nested(data, s.split(".")))
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a context manager when reading the sidecar text files (resource leak + ruff failures).

open(required_file) (line 131), open(halt_codes_file) (line 176), and open(contract_keys_file) (line 336) are passed directly to a list comprehension and never explicitly closed. CPython will close them eventually via refcount, but PyPy/long-running tests will leak FDs, and ruff is currently failing with SIM115 and PTH123 on all three sites. Switch to Path(...).read_text().splitlines() (or a with block) — same fix in check_halt_codes and drift_check.

♻️ Proposed pattern
+from pathlib import Path
@@
-    try:
-        required = [
-            line.strip()
-            for line in open(required_file)
-            if line.strip() and not line.startswith("#")
-        ]
-    except FileNotFoundError:
-        _fail(f"required sections file not found: {required_file}")
+    try:
+        lines = Path(required_file).read_text(encoding="utf-8").splitlines()
+    except FileNotFoundError:
+        _fail(f"required sections file not found: {required_file}")
+    required = [s.strip() for s in lines if s.strip() and not s.lstrip().startswith("#")]
🧰 Tools
🪛 GitHub Check: Lint & Format (Ruff + MyPy)

[failure] 131-131: ruff (PTH123)
ci/validate_schema.py:131:25: PTH123 open() should be replaced by Path.open()
help: Replace with Path.open()


[failure] 131-131: ruff (SIM115)
ci/validate_schema.py:131:25: SIM115 Use a context manager for opening files

🪛 GitHub Check: Refactoring Validation Gate

[failure] 131-131: ruff (PTH123)
ci/validate_schema.py:131:25: PTH123 open() should be replaced by Path.open()
help: Replace with Path.open()


[failure] 131-131: ruff (SIM115)
ci/validate_schema.py:131:25: SIM115 Use a context manager for opening files

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/validate_schema.py` around lines 128 - 140, The list-comprehension opens
sidecar files without closing them; replace direct open(...) usage with a safe
read pattern — e.g., use Path(required_file).read_text().splitlines() (or wrap
with with open(required_file) as f: f.readlines()) when building `required`, and
apply the same fix for `halt_codes_file` in `check_halt_codes` and
`contract_keys_file` in `drift_check` so those file handles are not leaked;
update the comprehension that builds `required` and the analogous comprehensions
in the `check_halt_codes` and `drift_check` code paths to use the chosen
safe-read approach.

Comment thread ci/validate_schema.py
Comment on lines +303 to +311
entries = data.get("entries")
if not isinstance(entries, list):
errors.append("entries must be a list")
else:
for i, entry in enumerate(entries):
for field in QUALITY_LOG_FIELDS:
if field not in entry:
errors.append(f"entry[{i}] missing required field {field!r}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate that each entry is a mapping before checking required fields.

If a quality-log entry is malformed (e.g. a string or list instead of a dict), field not in entry silently does substring/membership matching against the wrong type and the validator reports PASS. Add a type guard so non-dict entries fail loudly with CONTRACT_INTEGRITY_VIOLATION-style errors instead of being accepted.

     entries = data.get("entries")
     if not isinstance(entries, list):
         errors.append("entries must be a list")
     else:
         for i, entry in enumerate(entries):
+            if not isinstance(entry, dict):
+                errors.append(f"entry[{i}] must be a mapping, got {type(entry).__name__}")
+                continue
             for field in QUALITY_LOG_FIELDS:
                 if field not in entry:
                     errors.append(f"entry[{i}] missing required field {field!r}")

Side note: line 296 calls _fail("FAIL: quality log root must be a mapping", prefix="FAIL"), which prints FAIL: FAIL: .... Drop the embedded FAIL: from the message.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/validate_schema.py` around lines 303 - 311, When validating entries, first
ensure each entry is a mapping before checking required fields: in the loop that
iterates over entries (the variable entries and QUALITY_LOG_FIELDS), add a type
guard such as "if not isinstance(entry, Mapping): errors.append(f'entry[{i}]
must be a mapping (CONTRACT_INTEGRITY_VIOLATION)'); continue" so non-dict
entries fail loudly instead of performing membership checks; otherwise proceed
to check missing fields as before. Also remove the redundant "FAIL:" prefix from
the message passed into _fail (call site currently embeds "FAIL:"), leaving only
the descriptive message so the final output doesn't repeat "FAIL:".

Comment on lines +1 to +2
# ci/workflows/recursive-pass-ci.yml
# GMP-RSP-001 | L9 GMP v3.2.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Blocker: workflow must live at .github/workflows/recursive-pass-ci.yml, not github/workflows/.

GitHub Actions only auto-discovers workflow files under the dotfile directory .github/workflows/. Merging this file at github/workflows/recursive-pass-ci.yml will silently ship dead YAML — none of the gates (preflight, completeness, halt-codes, drift, unit-tests, evidence) will ever run, and branch-protection rules referring to the job name Full test suite — tests/test_pass_invariants.py (per the SETUP step 6) will never resolve. The PR description acknowledges this and proposes renaming on merge; please ensure the rename actually lands and is verified in the Actions tab on main post-merge, otherwise the entire CI gate is a no-op. Also update the leading comment on line 1 (# ci/workflows/recursive-pass-ci.yml) and SETUP line 559 once moved.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/workflows/recursive-pass-ci.yml` around lines 1 - 2, The workflow file
named recursive-pass-ci.yml is currently placed outside GitHub's auto-discovery
directory; move the file into the repository's workflows directory used by
GitHub Actions (so Actions will pick up the workflow), update the top-of-file
comment that currently reads "# ci/workflows/recursive-pass-ci.yml" to reflect
the new location, and update the SETUP reference mentioned around the file's
SETUP section (line referenced in the review) so it points to the new location;
after committing the move, verify the workflow appears and runs in the Actions
UI and that branch-protection jobs (e.g., "Full test suite —
tests/test_pass_invariants.py") resolve correctly.

Comment on lines +51 to +53
env:
PYTHON_VERSION: "3.11" # single override point; update here to change all jobs
VALIDATE_SCRIPT: ci/validate_schema.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Bump PYTHON_VERSION to 3.12 (or higher) to match project language baseline.

The validation script will be linted/typed under the project's mypy --strict and may use 3.12-only typing constructs (e.g. type aliases, PEP 695 generics) over time; keeping CI on 3.11 risks drift between local and CI behavior, and contradicts the stated language target.

-  PYTHON_VERSION: "3.11"   # single override point; update here to change all jobs
+  PYTHON_VERSION: "3.12"   # single override point; update here to change all jobs

As per coding guidelines: "Language: Python 3.12+" and "Python 3.12+ with async/await for all I/O operations".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env:
PYTHON_VERSION: "3.11" # single override point; update here to change all jobs
VALIDATE_SCRIPT: ci/validate_schema.py
env:
PYTHON_VERSION: "3.12" # single override point; update here to change all jobs
VALIDATE_SCRIPT: ci/validate_schema.py
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/workflows/recursive-pass-ci.yml` around lines 51 - 53, Update the CI
environment variable PYTHON_VERSION from "3.11" to "3.12" in the workflow env
block so the jobs run under the project's Python baseline; keep VALIDATE_SCRIPT
unchanged. Locate the env section containing PYTHON_VERSION and replace its
value to "3.12" (or higher if desired) so mypy/validate_schema.py and other
lint/type checks execute with Python 3.12 semantics.

Comment on lines +84 to +91
- name: Verify validate_schema.py exists
run: |
if [ ! -f "${{ env.VALIDATE_SCRIPT }}" ]; then
echo "❌ Missing: ${{ env.VALIDATE_SCRIPT }}" >&2
exit 1
fi
python ${{ env.VALIDATE_SCRIPT }} --help 2>/dev/null || true
echo "✅ validate_schema.py present"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preflight --help invocation is effectively a no-op.

validate_schema.py has no --help handler — passing it falls into the else branch and exits 1 with unknown mode: '--help'. The trailing || true then masks this and would also mask a real SyntaxError / ImportError, so the only thing this step actually verifies is file existence (already covered by the [ ! -f ... ] check). Either implement --help in the script (recommended; pairs naturally with the argparse refactor noted on validate_schema.py:374) or replace the smoke test with something that actually exits non-zero on a broken script, e.g. python -c "import importlib.util, sys; spec=importlib.util.spec_from_file_location('vs','${{ env.VALIDATE_SCRIPT }}'); m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/workflows/recursive-pass-ci.yml` around lines 84 - 91, The CI step
currently runs python ${{ env.VALIDATE_SCRIPT }} --help which is a no-op and
masked by the trailing || true; either add a proper --help handler to
validate_schema.py (e.g. use argparse and implement a --help/--mode docstring
around the main entry referenced near validate_schema.py:374) so the script
returns 0 for --help, or remove the flaky smoke test and replace it with a
robust module-import smoke test that executes the file without masking errors
(use importlib.util.spec_from_file_location + spec.loader.exec_module to load
${{ env.VALIDATE_SCRIPT }}), and ensure you drop the trailing || true so real
import/Syntax errors fail the workflow.

Comment on lines +514 to +542
python - << 'EOF'
import yaml, json, datetime, pathlib

specs = {
"pass-0": "specs/recursive-pass-0.yaml",
"pass-1": "specs/recursive-pass-1.yaml",
"pass-2": "specs/recursive-pass-2.yaml",
}
report = {
"generated_utc": datetime.datetime.utcnow().isoformat() + "Z",
"run_id": "${{ github.run_id }}",
"commit": "${{ github.sha }}",
"specs": {},
}
for name, path in specs.items():
try:
data = yaml.safe_load(open(path))
report["specs"][name] = {
"schema_version": data.get("schema_version", "MISSING"),
"yaml_valid": True,
}
except Exception as e:
report["specs"][name] = {"yaml_valid": False, "error": str(e)}

pathlib.Path("ci_evidence_report.json").write_text(
json.dumps(report, indent=2)
)
print("Evidence report written.")
EOF

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace datetime.utcnow() — deprecated in 3.12 and violates project datetime guideline.

datetime.datetime.utcnow() returns a naive datetime and is deprecated in Python 3.12; once PYTHON_VERSION is bumped this will emit DeprecationWarning and (eventually) be removed. Use timezone-aware UTC instead. While here, the bare except Exception and the open(path) without a context manager are also worth tightening since the same script is generating an evidence artifact that downstream consumers rely on.

-          import yaml, json, datetime, pathlib
+          import json
+          import pathlib
+          from datetime import UTC, datetime
+
+          import yaml
@@
-          report = {
-              "generated_utc": datetime.datetime.utcnow().isoformat() + "Z",
+          report = {
+              "generated_utc": datetime.now(tz=UTC).isoformat(),
               "run_id": "${{ github.run_id }}",
               "commit": "${{ github.sha }}",
               "specs": {},
           }
           for name, path in specs.items():
               try:
-                  data = yaml.safe_load(open(path))
+                  with pathlib.Path(path).open(encoding="utf-8") as f:
+                      data = yaml.safe_load(f)
                   report["specs"][name] = {
                       "schema_version": data.get("schema_version", "MISSING"),
                       "yaml_valid": True,
                   }
-              except Exception as e:
+              except (OSError, yaml.YAMLError) as e:
                   report["specs"][name] = {"yaml_valid": False, "error": str(e)}

As per coding guidelines: "Always use datetime.now(tz=UTC) for timezone-aware datetime operations".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@github/workflows/recursive-pass-ci.yml` around lines 514 - 542, The inline
Python step uses datetime.datetime.utcnow() (deprecated/naive), a bare except,
and open(path) without a context manager; update imports to include timezone (or
use datetime.now(tz=timezone.utc)) and replace
datetime.datetime.utcnow().isoformat() with a timezone-aware call (e.g.,
datetime.now(timezone.utc).isoformat() + "Z"); open each spec file with a
context manager (with open(path) as f: data = yaml.safe_load(f)); tighten error
handling by catching specific exceptions (e.g., yaml.YAMLError and OSError)
instead of bare except Exception as e and record their messages into
report["specs"][name].

Copy link
Copy Markdown
Collaborator Author

Workflow file was intentionally moved to github/workflows/recursive-pass-ci.yml without the leading dot. If this PR needs to be reverted during merge/review, undo that path placement. When ready to activate GitHub Actions, rename it to .github/workflows/recursive-pass-ci.yml.

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.

1 participant