Skip to content

freshness gate: overdue review is advisory, not a fleet-wide hard block - #2833

Merged
stranske merged 1 commit into
mainfrom
freshness-gate-advisory-staleness
Jul 25, 2026
Merged

freshness gate: overdue review is advisory, not a fleet-wide hard block#2833
stranske merged 1 commit into
mainfrom
freshness-gate-advisory-staleness

Conversation

@stranske

@stranske stranske commented Jul 25, 2026

Copy link
Copy Markdown
Owner

The problem (fleet-wide red main)

As of 2026-07-25 the registry review went overdue by one day (review_by=2026-07-24). That flipped tools/check_model_registry_freshness.py to a non-zero exit, and a unit test (test_direct_script_execution_uses_shared_default_profile) asserts it must be zero — so the required summary check failed on every open PR in the repo, none related to model selection.

The incumbents are all provisional and working fine as the runtime baseline. Nothing is broken. A "a review is due" signal is not a "this work is dangerous" signal, and it must not gate unrelated work. The tell that this is a defect: maint-77's scheduled path is explicitly written to never fail — it opens a tracking issue instead. A unit test quietly re-introduced the hard block through the pytest suite.

The fix — advisory vs. blocking findings

check_model_registry_freshness.py now splits findings:

  • Blocking (structural / dangerous): malformed registry, a selection or slot pointing at an absent/blocked model, an approved selection with no passing workload-benchmark evidence. → fails the gate.
  • Advisory (cadence): review_overdue, provisional_overdue, selection_review_overdue. → reported and surfaced, never fails the default gate.

The default gate exits non-zero only on blocking findings. --strict restores fail-on-any for the narrow case of gating a PR that itself edits model config. JSON output gains ok/blocking/advisory alongside the existing fresh/findings.

maint-77: only structural findings fail a model-config PR; the scheduled run still opens a non-blocking tracking issue for advisory staleness (new has_advisory output), so the overdue review is never silently dropped — just never blocks.

Why this is the right shape

  • Matches the principle: nothing stops work fleet-wide unless it signals that work would be dangerous or wrong.
  • The provisional incumbents remain the runtime baseline; no model selection is changed (respects "flag, don't unilaterally change selections").
  • A date rollover can never again redden the whole fleet.

Verification (local, CI-pinned)

  • Live config: default exit 0 (4 advisory, 0 blocking); --strict exit 1.
  • black==26.5.1 / ruff==0.15.20 / mypy==2.1.0 clean; maint-77 YAML valid.
  • 26 tests pass, including new assertions that an overdue review is advisory (exit 0) but --strict and structural findings still fail.

Unblocks #2831 and #2832 (and the rest of the open PRs). Follow-up context: the evaluation pilot that would supply real review evidence is exactly what #2831/#2832 enable.

Summary by CodeRabbit

  • New Features

    • Freshness checks now distinguish blocking structural issues from advisory overdue reviews.
    • Added strict mode to fail checks for any finding, including advisory items.
    • Reports now separate blocking and advisory findings for clearer review.
  • Bug Fixes

    • Advisory overdue reviews no longer unnecessarily block pull requests.
    • Scheduled checks continue tracking advisory items for follow-up.
  • Documentation

    • Documented the difference between advisory and blocking freshness findings.

A registry review whose review_by date simply passed (today it went overdue
by one day) flipped tools/check_model_registry_freshness.py to a non-zero
exit, which a unit test asserts must be zero — so the required `summary`
check failed on EVERY open PR fleet-wide, none of them related to model
selection. A "review is due" signal is not a "this work is dangerous"
signal and must not gate unrelated work. maint-77's scheduled path was
already written to never fail (it opens a tracking issue); a unit test
quietly re-introduced the hard block.

- check_model_registry_freshness.py: partition_findings() splits findings
  into BLOCKING (structural — malformed registry, absent/blocked model,
  approved selection with no passing evidence) and ADVISORY (cadence —
  review_overdue / provisional_overdue / selection_review_overdue). The
  default gate now exits non-zero only on blocking findings; --strict
  restores fail-on-any for callers gating a model-config change. JSON output
  gains ok/blocking/advisory alongside the existing fresh/findings keys.
- maint-77: only structural findings fail a model-config PR; the scheduled
  run still opens a non-blocking tracking issue for advisory staleness
  (new has_advisory output).
- tests + MODEL_SELECTION_POLICY.md updated for the advisory/blocking split.

Effect: the provisional incumbents remain the runtime baseline, the overdue
review is still surfaced (as a tracking issue), and a date rollover can no
longer redden the whole fleet. Verified on the live config: default exit 0
(4 advisory, 0 blocking), --strict exit 1; 26 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 25, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@stranske
stranske enabled auto-merge (squash) July 25, 2026 23:06
@stranske
stranske temporarily deployed to agent-high-privilege July 25, 2026 23:06 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The freshness gate now separates advisory overdue reviews from blocking structural findings, adds strict mode, updates tests and policy documentation, and propagates advisory status through the maintenance workflow for scheduled tracking-issue updates.

Changes

Freshness finding semantics

Layer / File(s) Summary
Classify and report freshness findings
tools/check_model_registry_freshness.py
Findings are partitioned into blocking and advisory categories; JSON and console output expose both, default exits fail only for blocking findings, and --strict fails for any finding.
Document and test exit semantics
tests/test_check_model_registry_freshness.py, docs/MODEL_SELECTION_POLICY.md
Tests cover advisory, strict, and structural outcomes, while policy documentation describes the updated gate behavior.
Propagate advisory status in Actions
.github/workflows/maint-77-model-registry-freshness.yml
The workflow exposes advisory status, continues failing pull requests for structural findings, and triggers scheduled tracking-issue updates for advisory findings.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant FreshnessGate
  participant FreshnessJob
  participant TrackingIssueJob
  FreshnessGate->>FreshnessJob: writes rc and has_advisory outputs
  FreshnessJob->>TrackingIssueJob: passes structural and advisory status
  TrackingIssueJob->>TrackingIssueJob: updates evidence-review issue when advisory status is true
Loading

Possibly related PRs

Suggested labels: github:actions, maintenance

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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 accurately summarizes the main change: overdue review findings are now advisory instead of blocking by default.
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.
✨ 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 freshness-gate-advisory-staleness

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

@agents-workflows-bot

Copy link
Copy Markdown
Contributor

Workflow source needed

PR #2833 needs either a linked GitHub issue or one valid non-issue Workflow Source before PR metadata automation can manage it safely.

Please do one of:

  • Add <!-- meta:issue:123 --> or a normal Closes #123 / Related to #123 line.
  • Check one Workflow Source option in the PR body.
  • Add a hidden marker such as <!-- workflow-source:local_request -->, <!-- workflow-source:manual_remote -->, <!-- workflow-source:review_followup -->, <!-- workflow-source:sync_campaign -->, or <!-- workflow-source:dependabot -->.
  • Add a workflow source label such as workflow:source-direct-pr, workflow:source-local-request, workflow:source-review-followup, workflow:source-sync, or workflow:no-automation.

Once a valid source is present, this warning will not be reposted.

@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: ce3c3a0d35

ℹ️ 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".

Comment on lines +42 to +44
"review_overdue",
"provisional_overdue",
"selection_review_overdue",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep invalid review dates blocking

When the registry date is missing or unparseable, _review_date emits review_overdue; a missing or invalid selection date similarly emits selection_review_overdue. Classifying these kinds wholesale as advisory therefore makes malformed model configuration exit 0 under the default gate, allowing a model-config PR with no valid review deadline to pass. Reserve these advisory kinds for successfully parsed dates that have merely elapsed, or emit distinct blocking kinds for missing/invalid dates.

Useful? React with 👍 / 👎.

@stranske-keepalive

Copy link
Copy Markdown
Contributor

Automated Status Summary

Head SHA: d11d5d3
Latest Runs: ⏳ pending — Gate
Required contexts: Gate / gate, Health 45 Agents Guard / guard
Required: core tests (3.12): ⏳ pending, core tests (3.13): ⏳ pending, docker smoke: ⏳ pending, gate: ⏳ pending

Workflow / Job Result Logs
(no jobs reported) ⏳ pending

Coverage Overview

  • Coverage history entries: 1

Coverage Trend

Metric Value
Current 75.74%
Baseline 85.00%
Delta -9.26%
Minimum 70.00%
Status ✅ Pass

Top Coverage Hotspots (lowest coverage)

File Coverage Missing
scripts/issue_dedup_smoke.py 0.0% 4
scripts/runner_lib/__main__.py 0.0% 3
scripts/validate_template_sync.py 0.0% 81
scripts/langchain/topic_splitter.py 19.1% 57
tools/codex_log_analyzer.py 19.6% 140
scripts/repo_review_round2_runner.py 25.1% 344
scripts/prune_agent_stubs.py 39.7% 26
scripts/repo_review_round1_runner.py 40.7% 133
tools/ensure_workflow_timeout_variables.py 42.1% 74
scripts/sync_label_docs.py 42.9% 64
tools/discover_model_catalog.py 44.8% 55
scripts/repo_review_backlog_scan.py 45.3% 116
scripts/repo_review_body_writer.py 46.5% 86
tools/codex_session_analyzer.py 47.9% 59
scripts/create_verifier_labels.py 48.3% 58

Low Coverage Files (<50.0%)

File Coverage Missing
scripts/issue_dedup_smoke.py 0.0% 4
scripts/runner_lib/__main__.py 0.0% 3
scripts/validate_template_sync.py 0.0% 81
scripts/langchain/topic_splitter.py 19.1% 57
tools/codex_log_analyzer.py 19.6% 140
scripts/repo_review_round2_runner.py 25.1% 344
scripts/prune_agent_stubs.py 39.7% 26
scripts/repo_review_round1_runner.py 40.7% 133
tools/ensure_workflow_timeout_variables.py 42.1% 74
scripts/sync_label_docs.py 42.9% 64
tools/discover_model_catalog.py 44.8% 55
scripts/repo_review_backlog_scan.py 45.3% 116
scripts/repo_review_body_writer.py 46.5% 86
tools/codex_session_analyzer.py 47.9% 59
scripts/create_verifier_labels.py 48.3% 58

Updated automatically; will refresh on subsequent CI/Docker completions.


Keepalive checklist

Scope

No scope information available

Tasks

  • No tasks defined

Acceptance criteria

  • No acceptance criteria defined

@stranske
stranske merged commit 4b26ce5 into main Jul 25, 2026
56 of 57 checks passed
@stranske
stranske deleted the freshness-gate-advisory-staleness branch July 25, 2026 23:10

@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: 3

🤖 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 @.github/workflows/maint-77-model-registry-freshness.yml:
- Around line 75-81: Add the freshness tool’s --strict flag to the pull-request
gate step that produces steps.gate.outputs.rc, ensuring advisory findings fail
model-config PRs. Leave the scheduled tracking-issue invocation non-strict, and
preserve the existing structural-finding failure behavior in “Fail PRs on
structural findings.”

In `@tests/test_check_model_registry_freshness.py`:
- Around line 322-332: Extend the freshness-gate tests around gate.main to
invoke --json and assert the response contract fields ok, blocking, and
advisory. Add cases covering provisional_overdue and selection_review_overdue in
addition to review_overdue, verifying their advisory behavior by default and
blocking behavior under --strict where applicable, while preserving the existing
structural finding assertion.

In `@tools/check_model_registry_freshness.py`:
- Around line 40-55: Update _review_date to emit distinct structural finding
kinds for unparseable dates and missing review_by/as_of values, while reserving
review_overdue for valid dates that have elapsed. Keep only genuinely temporal
overdue findings in ADVISORY_FINDING_KINDS so partition_findings treats
malformed or incomplete registries as blocking, and add regression tests
covering both invalid and missing date cases.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: d6d0c511-b60a-4bdc-92b8-47f220e1982c

📥 Commits

Reviewing files that changed from the base of the PR and between 9aa4404 and ce3c3a0.

📒 Files selected for processing (4)
  • .github/workflows/maint-77-model-registry-freshness.yml
  • docs/MODEL_SELECTION_POLICY.md
  • tests/test_check_model_registry_freshness.py
  • tools/check_model_registry_freshness.py

Comment on lines +75 to 81
# Only structural/dangerous findings fail a model-config PR. An overdue
# review is advisory and is surfaced by the scheduled tracking issue.
- name: Fail PRs on structural findings
if: github.event_name == 'pull_request' && steps.gate.outputs.rc == '1'
run: |
echo "::error::Model registry/slots are stale — see job summary."
echo "::error::Model registry/slots have a structural problem — see job summary."
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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

set -euo pipefail

echo "== Workflow file outline =="
fd -a 'maint-77-model-registry-freshness.yml' .github templates || true

echo "== Search for strict usage and model-config path checks =="
rg -n --hidden --glob '.github/workflows/*.yml' --glob '.github/workflows/*.yaml' --glob 'templates/consumer-repo/.github/workflows/*.yml' --glob 'templates/consumer-repo/.github/workflows/*.yaml' -- '--strict|model configuration|model-config|registry/slots|structural findings|advisory' .

echo "== Read target workflow around relevant lines =="
file=".github/workflows/maint-77-model-registry-freshness.yml"
if [ -f "$file" ]; then
  nl -ba "$file" | sed -n '1,140p'
fi

echo "== Read template counterpart if present =="
tfile="templates/consumer-repo/.github/workflows/maint-77-model-registry-freshness.yml"
if [ -f "$tfile" ]; then
  nl -ba "$tfile" | sed -n '1,140p'
fi

Repository: stranske/Workflows

Length of output: 2909


🏁 Script executed:

set -euo pipefail
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/maint-77-model-registry-freshness.yml')
print(p.exists())
if p.exists():
    text = p.read_text()
    for i, line in enumerate(text.splitlines(), 1):
        if '--strict' in line or 'gate' in line or 'pull_request' in line or 'model' in line:
            print(f'{i}: {line}')
PY

Repository: stranske/Workflows

Length of output: 2002


🏁 Script executed:

set -euo pipefail

echo "== template counterpart presence =="
fd -a 'maint-77-model-registry-freshness.yml' templates .github || true

echo "== workflow snippet with line numbers =="
sed -n '45,90p' .github/workflows/maint-77-model-registry-freshness.yml | cat -n

echo "== strict support in freshness tool =="
rg -n --hidden --glob 'tools/check_model_registry_freshness.py' -- '--strict|argparse|add_argument|rc|advisory|structural' tools/check_model_registry_freshness.py

echo "== tool file outline =="
python3 - <<'PY'
from pathlib import Path
p = Path('tools/check_model_registry_freshness.py')
print("exists:", p.exists())
if p.exists():
    for i, line in enumerate(p.read_text().splitlines(), 1):
        if any(k in line for k in ['--strict','argparse','add_argument','rc','advisory','structural','parser']):
            print(f'{i}: {line}')
PY

Repository: stranske/Workflows

Length of output: 9231


🏁 Script executed:

set -euo pipefail
rg -n --hidden --glob '.github/workflows/*' --glob 'templates/consumer-repo/.github/workflows/*' -- 'check_model_registry_freshness\.py|--strict' .

Repository: stranske/Workflows

Length of output: 847


Add --strict to the PR gate step. The freshness tool already supports strict mode, but this PR path still runs it without that flag, so advisory-only findings won’t fail model-config PRs. Keep the scheduled tracking-issue path non-strict.

🤖 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/maint-77-model-registry-freshness.yml around lines 75 -
81, Add the freshness tool’s --strict flag to the pull-request gate step that
produces steps.gate.outputs.rc, ensuring advisory findings fail model-config
PRs. Leave the scheduled tracking-issue invocation non-strict, and preserve the
existing structural-finding failure behavior in “Fail PRs on structural
findings.”

Source: Path instructions

Comment on lines +322 to 332
# An overdue review is advisory: it must NOT fail the default gate (so it
# cannot block unrelated fleet-wide work)...
registry_path.write_text(json.dumps(_registry(review_by="2026-07-01")), encoding="utf-8")
assert gate.main(common) == 0
# ...but --strict still fails on it, for callers gating a model-config change.
assert gate.main([*common, "--strict"]) == 1
# A structural finding (selection references an absent model) always blocks.
structural = _registry()
structural["selections"][0]["model_id"] = "missing"
registry_path.write_text(json.dumps(structural), encoding="utf-8")
assert gate.main(common) == 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.

🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add coverage for the new JSON and finding-category contract.

This test covers exit codes, but never invokes --json or asserts ok, blocking, and advisory. It also exercises only review_overdue; regressions affecting provisional_overdue or selection_review_overdue would pass unnoticed.

As per path instructions, changed Python behavior must have accompanying test coverage.

🧰 Tools
🪛 ast-grep (0.44.1)

[info] 323-323: use jsonify instead of json.dumps for JSON output
Context: json.dumps(_registry(review_by="2026-07-01"))
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)


[info] 330-330: use jsonify instead of json.dumps for JSON output
Context: json.dumps(structural)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 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 `@tests/test_check_model_registry_freshness.py` around lines 322 - 332, Extend
the freshness-gate tests around gate.main to invoke --json and assert the
response contract fields ok, blocking, and advisory. Add cases covering
provisional_overdue and selection_review_overdue in addition to review_overdue,
verifying their advisory behavior by default and blocking behavior under
--strict where applicable, while preserving the existing structural finding
assertion.

Source: Path instructions

Comment on lines +40 to +55
ADVISORY_FINDING_KINDS = frozenset(
{
"review_overdue",
"provisional_overdue",
"selection_review_overdue",
}
)


def partition_findings(
findings: list[dict[str, str]],
) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
"""Split findings into (blocking, advisory)."""
advisory = [f for f in findings if f.get("kind") in ADVISORY_FINDING_KINDS]
blocking = [f for f in findings if f.get("kind") not in ADVISORY_FINDING_KINDS]
return blocking, advisory

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Do not classify malformed review dates as advisory.

_review_date uses review_overdue for unparseable dates and missing review_by/as_of values at Lines [87-95]. Adding that kind to ADVISORY_FINDING_KINDS makes malformed or incomplete registries return ok: true and exit successfully, contradicting the blocking contract for malformed registries.

Use distinct structural finding kinds for invalid/missing dates and reserve review_overdue for a valid date that has merely elapsed. Add regression tests for both cases.

As per path instructions, changed Python behavior must prioritize correctness and test coverage.

Proposed fix
-        findings.append(_finding("review_overdue", f"unparseable registry date: {exc}"))
+        findings.append(_finding("registry_invalid", f"unparseable registry date: {exc}"))
...
-            "review_overdue",
+            "registry_invalid",
📝 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
ADVISORY_FINDING_KINDS = frozenset(
{
"review_overdue",
"provisional_overdue",
"selection_review_overdue",
}
)
def partition_findings(
findings: list[dict[str, str]],
) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
"""Split findings into (blocking, advisory)."""
advisory = [f for f in findings if f.get("kind") in ADVISORY_FINDING_KINDS]
blocking = [f for f in findings if f.get("kind") not in ADVISORY_FINDING_KINDS]
return blocking, advisory
ADVISORY_FINDING_KINDS = frozenset(
{
"registry_invalid",
"provisional_overdue",
"selection_review_overdue",
}
)
def partition_findings(
findings: list[dict[str, str]],
) -> tuple[list[dict[str, str]], list[dict[str, str]]]:
"""Split findings into (blocking, advisory)."""
advisory = [f for f in findings if f.get("kind") in ADVISORY_FINDING_KINDS]
blocking = [f for f in findings if f.get("kind") not in ADVISORY_FINDING_KINDS]
return blocking, advisory
🤖 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 `@tools/check_model_registry_freshness.py` around lines 40 - 55, Update
_review_date to emit distinct structural finding kinds for unparseable dates and
missing review_by/as_of values, while reserving review_overdue for valid dates
that have elapsed. Keep only genuinely temporal overdue findings in
ADVISORY_FINDING_KINDS so partition_findings treats malformed or incomplete
registries as blocking, and add regression tests covering both invalid and
missing date cases.

Source: Path instructions

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