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
1 change: 1 addition & 0 deletions docs/WORKFLOW_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ _Inline Gate helper_
- **Gate summary job (`pr-00-gate.yml`)** — Post-CI job that downloads artifacts, computes coverage deltas, runs the label-gated autofix routine, and updates the PR summary comment with a stable marker.

### Maintenance & Repo Health
- **Fleet Renovate intake** — `renovate-presets/fleet.json` bounds routine dependency work to Monday 01:00–05:00 America/Chicago, with two commits per hour and three concurrent branches/PRs. Trusted GitHub Actions digest/pin/minor/patch updates share the green-automerge lane; routine releases wait three days and for non-pending update-branch checks; major updates require explicit Dependency Dashboard approval. Vulnerability alerts bypass those routine delays, and grouped lock-file maintenance uses the same explicit weekly window.
- **`maint-39-test-llm-providers.yml`** — Manual dispatch harness to smoke-test GitHub Models and OpenAI credentials via `tools.llm_provider` before other maintenance runs; the workflow now avoids minting redundant GitHub App tokens so the test stays lightweight.
- **`maint-45-cosmetic-repair.yml`** — Manual dispatch utility that runs `pytest -q`, applies guard-gated cosmetic fixes via `scripts/ci_cosmetic_repair.py`, and (when not in dry-run mode) opens a labelled PR with the default workflow token—no extra GitHub App mint required.
- **`maint-46-post-ci.yml`** — Post-CI recovery watcher triggered by `workflow_run` on Gate completion. It inspects the Gate summary job before touching the repo, and only checks out helpers / installs the token-balanced API client when the summary leg actually failed, keeping the default token pool free unless recovery is required.
Expand Down
17 changes: 17 additions & 0 deletions docs/ci/TOOL_VERSION_MANAGEMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,23 @@ All CI and autofix workflows use tool versions defined in a single source of tru
- CI autofix loop
- Local development

## Renovate Intake Validation

Renovate owns routine dependency updates, while `autofix-versions.env` remains the
source for the dev-tool pins it explicitly excludes. The fleet preset uses a bounded
weekly intake window, conservative branch/PR budgets, release-age and update-branch-check
gates, a grouped trusted GitHub Actions lane, and Dependency Dashboard approval for
majors. Vulnerability alerts bypass the routine window and age gate.

Run the same validation used by `scripts/dev_check.sh` after editing either Renovate
entrypoint or the shared preset:

```bash
npx --yes --package renovate@43.285.3 -- renovate-config-validator --no-global \
renovate.json renovate-presets/fleet.json \
templates/consumer-repo/.github/renovate.json
```

## Version File

**Location**: `.github/workflows/autofix-versions.env`
Expand Down
10 changes: 10 additions & 0 deletions docs/ops/CONSUMER_REPO_MAINTENANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,16 @@ Maint 68 implements these exceptions through each entry's typed manifest
`skip_repos` rules. There is no separate hard-coded custom-Gate list in the
sync script.

### Fleet Renovate intake policy

All registered consumers extend `renovate-presets/fleet.json`. Routine dependency
work is limited to Monday 01:00–05:00 America/Chicago, two commits per hour, and
three concurrent branches/PRs. Routine releases wait three days and for non-pending
update-branch checks; vulnerability alerts bypass each routine delay. Trusted GitHub Actions
digest, pin, minor, and patch updates are grouped for green automerge, while majors
stay visible in the Dependency Dashboard until explicitly approved. Lock-file
maintenance is grouped into the same weekly maintenance window.

---

## Bug Triage Process
Expand Down
24 changes: 24 additions & 0 deletions renovate-presets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Fleet Renovate preset

`fleet.json` is the single source of truth for Renovate intake across Workflows and
the registered consumer repositories. It opens routine dependency work only in the
Monday 01:00–05:00 America/Chicago maintenance window, permits at most three routine
branches and PRs, and limits commits to two per hour.

Routine releases must be at least three days old and wait until their update-branch
checks are not pending. Vulnerability alerts bypass the window, release-age delay,
and update-branch check gate so security remediation is never held by the routine
budget.

Trusted GitHub Actions digest, pin, minor, and patch updates share one green-automerge
lane. Major updates remain visible in the Dependency Dashboard and create a PR only
after an explicit dashboard approval. Lock-file maintenance is a separate, grouped
weekly lane in the same window.

Validate the repository config before review:

```bash
npx --yes --package renovate@43.285.3 -- renovate-config-validator --no-global \
renovate.json renovate-presets/fleet.json \
templates/consumer-repo/.github/renovate.json
```
28 changes: 26 additions & 2 deletions renovate-presets/fleet.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
"extends": ["config:recommended"],
"platformAutomerge": true,
"labels": ["dependencies"],
"timezone": "America/Chicago",
"schedule": ["after 1am and before 5am on monday"],
"commitHourlyLimit": 2,
"prConcurrentLimit": 3,
"branchConcurrentLimit": 3,
"minimumReleaseAge": "3 days",
"prCreation": "not-pending",
Comment thread
stranske marked this conversation as resolved.
"dependencyDashboard": true,
"vulnerabilityAlerts": {
"schedule": [],
"minimumReleaseAge": null,
"prCreation": "immediate"
},
"lockFileMaintenance": {
"enabled": true,
"groupName": "weekly lock file maintenance",
"schedule": ["after 1am and before 5am on monday"]
},
"pip-compile": {
"managerFilePatterns": ["/(^|/)requirements\\.lock$/"]
},
Expand All @@ -27,12 +45,18 @@
"automergeType": "pr"
},
{
"description": "GitHub Actions minor+patch: group + automerge on green",
"description": "Trusted GitHub Actions digest, pin, minor, and patch updates: group + automerge on green",
"matchManagers": ["github-actions"],
"matchUpdateTypes": ["minor", "patch"],
"matchUpdateTypes": ["digest", "pin", "minor", "patch"],
"groupName": "github-actions",
"automerge": true
},
{
"description": "Major updates remain visible in the Dependency Dashboard until a maintainer approves the specific upgrade",
"matchUpdateTypes": ["major"],
"dependencyDashboardApproval": true,
"automerge": false
},
{
"description": "Vendored minimatch cascade — keep minimatch, brace-expansion, balanced-match in one PR",
"matchFileNames": [".github/scripts/**"],
Expand Down
23 changes: 22 additions & 1 deletion scripts/dev_check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,25 @@ if [[ "$CHANGED_ONLY" == true ]]; then
UNSTAGED_FILES=$(git diff --name-only 2>/dev/null | grep -E '\.(py)$' 2>/dev/null | grep -v -E '^(archive/|\.extraction/)' 2>/dev/null || echo "")
ALL_FILES=$(echo -e "$PYTHON_FILES\n$UNSTAGED_FILES" | sort -u | grep -v '^$' 2>/dev/null || echo "")

if [[ -z "$ALL_FILES" ]]; then
# Renovate settings are JSON, so they are deliberately absent from the
# Python formatter/linter target list. They must still keep this command
# running long enough to reach the dedicated validator below.
RENOVATE_CONFIG_CHANGED=$( {
git diff --name-only HEAD~1 2>/dev/null
git diff --name-only 2>/dev/null
} | grep -E '^(renovate\.json|renovate-presets/.*\.json|templates/consumer-repo/\.github/renovate\.json)$' | sort -u || echo "")

if [[ -z "$ALL_FILES" && -z "$RENOVATE_CONFIG_CHANGED" ]]; then
echo -e "${GREEN}No Python files changed (excluding old folders) - nothing to check${NC}"
exit 0
fi

if [[ -n "$RENOVATE_CONFIG_CHANGED" && -z "$ALL_FILES" ]]; then
echo -e "${BLUE}Renovate configuration changed; continuing to its dedicated validator:${NC}"
echo "$RENOVATE_CONFIG_CHANGED" | sed 's/^/ /'
echo ""
fi

echo -e "${BLUE}Checking only changed files (excluding old folders):${NC}"
echo "$ALL_FILES" | sed 's/^/ /'
echo ""
Expand Down Expand Up @@ -570,6 +584,13 @@ else
echo -e "${YELLOW}⚠ Node.js not available; skipping keepalive harness tests${NC}"
fi

echo -e "${BLUE}7. Renovate configuration...${NC}"
if command -v npx >/dev/null 2>&1; then
quick_check "Renovate configuration" "npx --yes --package renovate@43.285.3 -- renovate-config-validator --no-global renovate.json renovate-presets/fleet.json templates/consumer-repo/.github/renovate.json" ""
Comment thread
stranske marked this conversation as resolved.
else
echo -e "${YELLOW}⚠ npx not available; skipping Renovate configuration validation${NC}"
fi

echo ""
echo -e "${CYAN}=== Quick Check Complete ===${NC}"
echo -e "${BLUE}For comprehensive validation, run: ./scripts/check_branch.sh${NC}"
Expand Down
99 changes: 99 additions & 0 deletions tests/workflows/test_renovate_fleet_policy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import json
from pathlib import Path

PRESET_PATH = Path(__file__).resolve().parents[2] / "renovate-presets" / "fleet.json"
REPO_ROOT = PRESET_PATH.parents[1]


def _preset() -> dict:
return json.loads(PRESET_PATH.read_text(encoding="utf-8"))


def _rule(preset: dict, **expected: object) -> dict:
matches = [
rule
for rule in preset["packageRules"]
if all(rule.get(key) == value for key, value in expected.items())
]
assert len(matches) == 1
return matches[0]


def test_fleet_renovate_intake_budget_is_bounded_to_the_weekly_window() -> None:
preset = _preset()

assert preset["timezone"] == "America/Chicago"
assert preset["schedule"] == ["after 1am and before 5am on monday"]
assert preset["commitHourlyLimit"] == 2
assert preset["prConcurrentLimit"] == 3
assert preset["branchConcurrentLimit"] == 3
assert preset["prCreation"] == "not-pending"
assert preset["minimumReleaseAge"] == "3 days"


def test_trusted_action_digests_are_grouped() -> None:
rule = _rule(_preset(), matchManagers=["github-actions"])

assert rule["matchUpdateTypes"] == ["digest", "pin", "minor", "patch"]
assert rule["groupName"] == "github-actions"
assert rule["automerge"] is True


def test_fleet_preset_keeps_workflows_owned_dev_tool_pins_out_of_renovate() -> None:
rule = _rule(_preset(), enabled=False)

assert rule["matchPackageNames"] == [
"ruff",
"black",
"mypy",
"pytest",
"pytest-cov",
"pytest-xdist",
"coverage",
"isort",
"docformatter",
]


def test_vulnerability_alerts_bypass_routine_intake_delays() -> None:
alerts = _preset()["vulnerabilityAlerts"]

assert alerts == {
"schedule": [],
"minimumReleaseAge": None,
"prCreation": "immediate",
}


def test_major_updates_stay_visible_but_require_dashboard_approval() -> None:
preset = _preset()
rule = _rule(preset, matchUpdateTypes=["major"])

assert preset["dependencyDashboard"] is True
assert rule["dependencyDashboardApproval"] is True
assert rule["automerge"] is False


def test_lock_file_maintenance_has_the_same_explicit_weekly_cadence() -> None:
Comment thread
stranske marked this conversation as resolved.
lock_maintenance = _preset()["lockFileMaintenance"]

assert lock_maintenance == {
"enabled": True,
"groupName": "weekly lock file maintenance",
"schedule": ["after 1am and before 5am on monday"],
}


def test_workflows_and_consumer_entrypoints_share_the_bounded_fleet_policy() -> None:
expected_preset = "github>stranske/Workflows//renovate-presets/fleet"
entrypoints = (
REPO_ROOT / "renovate.json",
REPO_ROOT / "templates" / "consumer-repo" / ".github" / "renovate.json",
)

for entrypoint in entrypoints:
config = json.loads(entrypoint.read_text(encoding="utf-8"))
assert config["extends"] == [expected_preset]

preset = _preset()
assert preset["prConcurrentLimit"] == preset["branchConcurrentLimit"] == 3
Loading