fix(ci): make workflow cron the single Renovate schedule#3
Merged
Conversation
The previous setup had two scheduling layers that didn't overlap: the workflow cron fired Mondays at 09:00 UTC, while renovate.json5's `schedule: ["before 6am every weekday"]` only created PRs before 06:00 UTC. Every cron-triggered run landed three hours after the window closed and Renovate held all PRs in "Awaiting Schedule" — including the manually-dispatched runs we used during the soft-launch exercise. For self-hosted Renovate, the config-side `schedule:` field is redundant: Renovate only runs when the workflow fires, so the workflow cron already controls cadence. Removing the second layer: - Eliminates the alignment trap (the failure mode is silent — runs "succeed" while creating zero PRs). - Makes `workflow_dispatch` actually useful — manual triggers now create PRs immediately, regardless of clock time. - Removes the dependency on Dependency-Dashboard checkbox ticking to bypass the schedule for ad-hoc runs. Cron moved from `0 9 * * 1` (Mon 09:00) to `0 5 * * 1-5` (Mon-Fri 05:00) so PRs land before standup. RENOVATE.md updated to document the single-source-of-truth design and call out the rationale.
Adds `minimumReleaseAge: "3 days"` globally and raises it to 7 days for the two auto-merge rules. Defends against malicious-publish ratchet attacks where a poisoned version goes live for hours then gets yanked (event-stream, colors.js, node-ipc, etc.). Trade-off: legitimate CVE patches land 3 days later than they would otherwise; for a project whose security-relevant deps come entirely from upstream tooling we don't write ourselves, the marginal protection is worth it. `internalChecksFilter: "strict"` excludes too-young releases rather than parking them in the Dependency Dashboard as "pending", keeping the dashboard signal clean — it shows what's about to land, not what's blocked behind cooldown. Auto-merge rules carry the higher 7-day cooldown because those updates skip human review; the cooldown is the only defense layer left if an attacker pushes a malicious patch.
This was referenced May 4, 2026
njhensley
added a commit
that referenced
this pull request
May 4, 2026
The `actions/checkout` step was modeled on NVIDIA/gpu-operator's renovate workflow, but our setup deliberately does not pass `configurationFile:` (to avoid customManager doubling — see PR #3). Without that input, the renovatebot/github-action clones the repo itself inside Docker and has no need for an outer working tree. Observed failure: every Renovate run since the practice exercise started would create the first eligible branch (e.g. `renovate/build-tools`) and then abort 0.3s later with "Repository has changed during renovation" — even though no commit to main happened during the run window. The credentials and working tree planted at /home/runner/work by `actions/checkout` are the suspect: Renovate's container-internal git operations and the runner-side git state cross-contaminate, and Renovate misinterprets its own branch push as a base-branch advance. Removing the step removes the cross-contamination surface. The action documents that checkout is not required when using its default internal-clone mode.
njhensley
added a commit
that referenced
this pull request
May 13, 2026
Audit findings #2, #3, #4 against generated evidence bundles: predicate.validatorImages was always null, predicate.validatorCatalogVersion was always empty, and the pointer's signer block emitted zero-valued identity/issuer + rekorLogIndex:0 for unsigned bundles — making a real Rekor log index 0 indistinguishable from 'no Rekor entry' and an unsigned bundle indistinguishable from one whose signer fields failed to extract. Changes: - emitRecipeEvidence loads the validator catalog once and feeds it to both the BOM (chart refs + validator images) and the predicate's ValidatorCatalogVersion + ValidatorImages fields. Shared dedupValidatorImages helper collapses the catalog's one-entry-per-check duplication (deployment/perf/conformance all share images). - PointerAttestation.Signer becomes *PointerSigner, omitempty. Unsigned bundles emit the attestation entry with no signer block at all; signed bundles always carry non-nil Signer with populated Identity and Issuer. - PointerSigner.RekorLogIndex becomes *int64, omitempty. nil = no Rekor entry created (--no-rekor signing path); non-nil = real Rekor log index, even when zero (Rekor's first-ever entry occupies index 0 and that's a legitimate position a consumer should be able to verify). - signAndPushBundle's no-push path returns signPushOutcome{} rather than signPushOutcome{Sign: &SignResult{}}, matching its existing 'All fields are nil when --push is absent' comment and letting buildPointerInputs do the right nil-check. Test coverage: - pkg/evidence/attestation/pointer_test.go: unsigned bundles MUST omit the signer YAML block; signed-without-rekor bundles MUST omit rekorLogIndex. Both assertions check rendered YAML, not just the struct. - pkg/cli/validate_evidence_test.go (new): catalogVersion, dedupValidatorImages, validatorImagesForPredicate helpers; and buildPointerInputs covers the three outcome shapes (unsigned, signed-with-rekor, signed-without-rekor). Notes: - ValidatorImages.Digest remains blank in this PR. The catalog records image refs by tag, not by digest; resolving each ref to a digest needs a registry round-trip per image, which validate's hot path avoids. Audit item #10 (BOM image digests) tracks the longer-term resolver path; the same resolver will populate this field when it lands.
njhensley
added a commit
that referenced
this pull request
May 13, 2026
Audit findings #2, #3, #4 against generated evidence bundles: predicate.validatorImages was always null, predicate.validatorCatalogVersion was always empty, and the pointer's signer block emitted zero-valued identity/issuer + rekorLogIndex:0 for unsigned bundles — making a real Rekor log index 0 indistinguishable from 'no Rekor entry' and an unsigned bundle indistinguishable from one whose signer fields failed to extract. Changes: - emitRecipeEvidence loads the validator catalog once and feeds it to both the BOM (chart refs + validator images) and the predicate's ValidatorCatalogVersion + ValidatorImages fields. Shared dedupValidatorImages helper collapses the catalog's one-entry-per-check duplication (deployment/perf/conformance all share images). - PointerAttestation.Signer becomes *PointerSigner, omitempty. Unsigned bundles emit the attestation entry with no signer block at all; signed bundles always carry non-nil Signer with populated Identity and Issuer. - PointerSigner.RekorLogIndex becomes *int64, omitempty. nil = no Rekor entry created (--no-rekor signing path); non-nil = real Rekor log index, even when zero (Rekor's first-ever entry occupies index 0 and that's a legitimate position a consumer should be able to verify). - signAndPushBundle's no-push path returns signPushOutcome{} rather than signPushOutcome{Sign: &SignResult{}}, matching its existing 'All fields are nil when --push is absent' comment and letting buildPointerInputs do the right nil-check. Test coverage: - pkg/evidence/attestation/pointer_test.go: unsigned bundles MUST omit the signer YAML block; signed-without-rekor bundles MUST omit rekorLogIndex. Both assertions check rendered YAML, not just the struct. - pkg/cli/validate_evidence_test.go (new): catalogVersion, dedupValidatorImages, validatorImagesForPredicate helpers; and buildPointerInputs covers the three outcome shapes (unsigned, signed-with-rekor, signed-without-rekor). Notes: - ValidatorImages.Digest remains blank in this PR. The catalog records image refs by tag, not by digest; resolving each ref to a digest needs a registry round-trip per image, which validate's hot path avoids. Audit item #10 (BOM image digests) tracks the longer-term resolver path; the same resolver will populate this field when it lands.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes the second-layer
schedule:field from.github/renovate.json5and aligns the workflow cron to the desired cadence. The workflow cron is now the single source of truth for when Renovate runs.Motivation / Context
During the practice run on this fork (PR #1), Renovate workflow runs all completed successfully but created zero PRs — every queued bundle stayed in "Awaiting Schedule". Root cause: the two scheduling layers didn't overlap.
.github/workflows/renovate.yamlcron: "0 9 * * 1".github/renovate.json5schedule: ["before 6am every weekday"]The cron-triggered run landed three hours after Renovate's window closed, so all PRs were held.
workflow_dispatchruns hit the same problem unless the operator was lucky enough to dispatch within the 6-hour weekday window.For self-hosted Renovate, the config-side
schedule:is redundant: Renovate only runs when the workflow fires. Removing the second layer eliminates the alignment trap and makesworkflow_dispatchactually useful for ad-hoc runs.Fixes: silently-empty Renovate runs in PR #1
Related: #1
Type of Change
Component(s) Affected
Implementation Notes
schedule:andtimezone:fromrenovate.json5. Replaced with a comment block explaining the single-source design and pointing at the workflow cron.0 9 * * 1(Mon 09:00 UTC) →0 5 * * 1-5(Mon–Fri 05:00 UTC). Weekday cadence keeps the CVE-to-PR window tight (self-hosted Renovate can't ingest GitHub vulnerability alerts), and 05:00 UTC means PRs land before standup in most US/EU time zones..github/RENOVATE.mdArchitecture and Schedule sections to reflect the new design.Testing
After merge, expected behavior:
workflow_dispatchwill create all 15 queued bundles immediately (no schedule gating, no checkbox dance).Risk Assessment
Rollout notes: N/A. Effect is immediate on next Renovate run.
Checklist
make lint-renovate,actionlint).github/RENOVATE.mdArchitecture + Schedule sections)git commit -S)