IMPL 0008: Distributed Renovate via Per-Repo GitHub Actions
Status: Draft Author: Donald Gifford Date: 2026-03-15
Objective
Add Renovate file management to repo-guardian so that every repository gets a standardized Renovate workflow and config. repo-guardian manages these as ordinary file rules — if missing, PR them in; if drifted, detect it. No new engine capabilities are required; this is purely templates, default rules, assertions, and tests.
Implements: DESIGN-0009
Scope
In Scope
- Renovate workflow template (docker-based GitHub Actions workflow)
- Renovate config template (JSON extending org preset)
- Updated policy defaults with two new file rules (workflow + config)
- Content assertion validating org preset reference in
renovate.json workflow_syncreconciler attached to the workflow rule (already exists)- Unit tests for assertion pattern matching on
renovate.json - Integration tests for engine evaluating both renovate file rules
- Example HCL configuration in documentation
- Updated embedded template for existing
renovate.tmpl
Out of Scope
- Coordinated dispatch / trigger endpoints (DESIGN-0009 Future Work)
- Staggered cron scheduling (DESIGN-0009 Future Work)
- Slack integration (DESIGN-0009 Future Work)
- Renovate GitHub App setup / org secret configuration (operational)
- Org preset repo creation (
renovate-config/default.json) (operational) - Template variable substitution or per-repo rendering
- PR comment tracking for repeated non-compliance (future engine feature)
- Changes to the checker engine, policy loader, or reconciler system
(beyond adding the
orgfield toGuardianConfig)
Implementation Phases
Each phase builds on the previous one. A phase is complete when all its tasks are checked off and its success criteria are met.
Phase 1: Embedded Templates
Create the two embedded templates that repo-guardian will use to PR Renovate files into repositories.
Tasks
- [x] Create
internal/rules/templates/renovate-workflow.tmplwith the docker-based GitHub Actions workflow from DESIGN-0009 (schedule trigger,actions/create-github-app-token@v1,docker run renovate/renovate:latest,RENOVATE_REPOSITORIESscoped to${{ github.repository }}) - [x] Update
internal/rules/templates/renovate.tmplto use the org preset pattern ("extends": ["github>ORG_NAME/renovate-config"]) instead of the current"extends": ["config:recommended"]. TheORG_NAMEplaceholder is replaced at template load time using the configured org name (see Phase 2) - [x] Verify templates load correctly via
TemplateStoreby checking thatGet("renovate-workflow")andGet("renovate")return expected content
Success Criteria
go build ./...succeeds- Both templates are accessible via
TemplateStore.Get() - Template content matches DESIGN-0009 specification
make lintpasses
Phase 2: Policy Default Rules
Update the built-in policy defaults to include two Renovate file rules: one for the workflow (exact match) and one for the config (contains with assertion).
Tasks
- [x] Add
orgfield toGuardianConfigininternal/policy/types.go(hcl:"org,optional") with env var overrideGITHUB_ORGapplied in the loader. This is used for org-specific assertion patterns and template rendering. - [x] Update
defaultRenovateRule()ininternal/policy/defaults.goto return therenovate_configrule:check = "contains",paths = ["renovate.json", "renovate.json5", ".renovaterc", ".renovaterc.json", ".github/renovate.json", ".github/renovate.json5"](search all standard locations),target = "renovate.json",template = "renovate", with an assertion matchinggithub>ORG_NAME/renovate-config(using configured org name) and message "renovate.json must extend org preset" - [x] Add
defaultRenovateWorkflowRule()ininternal/policy/defaults.goreturning therenovate_workflowrule:check = "exact",paths = [".github/workflows/renovate.yml"],target = ".github/workflows/renovate.yml",template = "renovate-workflow", with aworkflow_syncreconciler (watch = true) - [x] Both rules should default to
enabled = false(opt-in, same as current Renovate rule behavior) - [x] Add the workflow rule to the
DefaultPolicyConfig()function'sFileRulesslice - [x] Update existing tests in
internal/policy/defaults_test.goto cover both new rules, assertion config, and reconciler config - [x] Run
make lint && make testto verify
Success Criteria
DefaultPolicyConfig()includes bothrenovate_configandrenovate_workflowfile rules- Both rules are disabled by default
GuardianConfig.Orgis populated from HCL orGITHUB_ORGenv varrenovate_configrule has a compiled assertion for org-specific preset pattern (e.g.,github>donaldgifford/renovate-config)renovate_workflowrule has aworkflow_syncreconciler withwatch = true- All existing tests pass, new defaults tests pass
make lintpasses
Phase 3: Assertion and Template Tests
Write focused unit tests validating the assertion pattern works correctly
against various renovate.json content and that the exact-match check
mode works with the workflow template.
Tasks
- [x] Write unit tests in
internal/policy/assertion_test.gofor the Renovate org preset assertion pattern (github>donaldgifford/renovate-config): - [x] Valid:
{"extends": ["github>donaldgifford/renovate-config"]}— passes - [x] Valid:
{"extends": ["github>donaldgifford/renovate-config"], "labels": ["deps"]}— passes (overrides allowed) - [x] Invalid:
{"extends": ["config:recommended"]}— fails - [x] Invalid:
{"extends": ["github>wrongorg/renovate-config"]}— fails (wrong org) - [x] Invalid:
{}— fails (no extends) - [x] Invalid: empty string — fails
- [x] Write unit test in
internal/checker/engine_policy_test.goforevaluateExact()with the renovate workflow template — verify that identical content passes and modified content triggers action - [x] Write unit test in
internal/checker/engine_policy_test.goforevaluateContains()with the renovate config — verify that present file with valid assertion passes and invalid assertion triggers action - [x] Run
make testto verify all tests pass
Success Criteria
- Assertion pattern correctly matches valid org preset references
- Assertion pattern correctly rejects content without org preset
- Exact-match check works with YAML workflow content
- Contains check with assertion works with JSON config content
make testpassesmake lintpasses
Phase 4: Integration Tests
Write end-to-end integration tests that exercise the full engine pipeline with both Renovate file rules, including PR creation and reconciler triggering.
Tasks
- [x] Write integration test in
internal/checker/engine_policy_test.go:TestIntegration_RenovateFileRules_BothMissing— engine detects both files missing, creates single PR containing bothrenovate.ymlandrenovate.jsonwith correct template content - [x] Write integration test:
TestIntegration_RenovateFileRules_WorkflowMissing— engine detects missingrenovate.yml(config exists and valid), creates PR with just the workflow file - [x] Write integration test:
TestIntegration_RenovateFileRules_ConfigMissing— engine detects missingrenovate.json, creates PR with correct template content - [x] Write integration test:
TestIntegration_RenovateFileRules_ConfigInvalidPreset— engine detectsrenovate.jsonexists but fails assertion (missing org preset reference), creates PR - [x] Write integration test:
TestIntegration_RenovateFileRules_WorkflowDrifted— engine detectsrenovate.ymlexists but differs from template (exact mode), creates PR - [x] Write integration test:
TestIntegration_RenovateFileRules_AllPresent— both files exist and pass checks, no PR created, reconciler runs - [x] Run
make testto verify all integration tests pass
Success Criteria
- All six integration test scenarios pass
- Both-missing scenario produces single PR with both files
- PR creation includes correct file content from templates
- Assertion failure on
renovate.jsontriggers PR - Exact-match drift on
renovate.ymltriggers PR - Reconciler runs when files are present and valid
make testpassesmake lintpasses
Phase 5: Documentation and HCL Examples
Update documentation with HCL configuration examples, template descriptions, and rollout guidance.
Tasks
-
[x] Add example HCL configuration to
docs/or project README showing how to enable the Renovate file rules inguardian.hcl: ```hcl guardian { org = "donaldgifford" # or set GITHUB_ORG env var }rule "file" "renovate_workflow" { enabled = true check = "exact" paths = [".github/workflows/renovate.yml"] target = ".github/workflows/renovate.yml" template = "renovate-workflow" reconcile "workflow_sync" { watch = true } } rule "file" "renovate_config" { enabled = true check = "contains" paths = ["renovate.json", "renovate.json5", ".renovaterc", ".renovaterc.json", ".github/renovate.json", ".github/renovate.json5"] target = "renovate.json" template = "renovate" assertion { pattern = "github>donaldgifford/renovate-config" message = "renovate.json must extend org preset" } }
`` - [x] Document therenovate-workflowandrenovatetemplate names and what they contain - [x] Document that both rules are disabled by default and must be opted in via HCL config or by modifying defaults - [x] Update DESIGN-0009 status from "Draft" to "Approved" (or as appropriate after review) - [x] Rundocz updateto regenerate README index tables - [x] Runmake lint` to verify no markdown issues
Success Criteria
- HCL example is documented and correct
- Template contents and names are documented
- Opt-in behavior is clearly documented
- Doc indexes are up to date
make lintpasses
Phase 6: Final Validation
Full CI pipeline validation and cleanup.
Tasks
- [x] Run
make ci(lint + test + build) — all green - [x] Review test coverage for changed packages (
internal/policy/,internal/rules/,internal/checker/) - [x] Verify no TODO/FIXME comments left from implementation
- [x] Verify
renovate-workflow.tmplcontent exactly matches DESIGN-0009 workflow YAML - [x] Verify
renovate.tmplcontent matches DESIGN-0009 config JSON - [x] Update this implementation doc status to "Completed"
- [x] Commit all changes with conventional commit message
Success Criteria
make cipasses with zero errors- All templates match DESIGN-0009 specification
- No unresolved TODOs related to this implementation
- Implementation doc marked as Completed
File Changes
| File | Action | Description |
|---|---|---|
internal/rules/templates/renovate-workflow.tmpl |
Create | Docker-based GHA workflow for Renovate |
internal/rules/templates/renovate.tmpl |
Modify | Update to org preset JSON pattern |
internal/policy/types.go |
Modify | Add Org field to GuardianConfig |
internal/policy/loader.go |
Modify | Add GITHUB_ORG env var override for Org field |
internal/policy/defaults.go |
Modify | Add renovate_workflow rule, update renovate rule with assertion |
internal/policy/defaults_test.go |
Modify | Tests for new default rules |
internal/policy/assertion_test.go |
Modify | Tests for org preset assertion pattern |
internal/checker/engine_policy_test.go |
Modify | Integration tests for Renovate file rules |
internal/config/config.go |
Modify | Add GITHUB_ORG env var (if not using HCL loader path) |
Testing Plan
- [x] Unit tests for org preset assertion pattern (6 cases: 2 valid, 4 invalid including wrong org)
- [x] Unit tests for exact-match check on workflow template
- [x] Unit tests for contains check with assertion on config
- [x] Unit tests for default rule configuration (both rules, org config)
- [x] Integration tests for engine pipeline (6 scenarios: both missing, missing workflow, missing config, invalid config, drifted workflow, all present)
Resolved Questions
-
Org name configuration: Add an
orgfield to theguardian {}HCL block withGITHUB_ORGenv var override. This value is used in assertion patterns and template rendering for the org preset reference. Works for both GitHub organizations and personal accounts (e.g.,donaldgifford). Config-first, env var to override. -
Renovate config search paths: Keep all 6 standard Renovate config locations (
renovate.json,renovate.json5,.renovaterc,.renovaterc.json,.github/renovate.json,.github/renovate.json5). Thepathsfield searches all locations to detect any existing config. Thetargetisrenovate.json(where a new file is created if none exist). The CI workflow'sRENOVATE_CONFIG_FILEis set torenovate.json, but Renovate CLI auto-discovers any of these locations. -
PR comment tracking for non-compliance: Out of scope for this implementation. Adding comments to existing PRs noting repeated non-compliance ("still invalid on check N") is a useful feature but applies to all file rules, not just Renovate. Tracked as future work for the checker engine.
-
Assertion pattern specificity: Use the configured org name for a specific match pattern (e.g.,
github>donaldgifford/renovate-config) rather than a generic wildcard. This prevents repos from satisfying the assertion with a preset from a different org. -
PR search terms for workflow rule: No search terms needed on the workflow rule. The engine already detects open PRs via the deterministic
repo-guardian/add-missing-filesbranch name, which covers both files. The config rule keeps its existing["renovate"]search terms from the current defaults.
Dependencies
- DESIGN-0009 (approved or in review)
- Existing
workflow_syncreconciler (already implemented, IMPL-0007) - Existing assertion system (already implemented, IMPL-0007)
- Existing
check = "exact"andcheck = "contains"modes (already implemented, IMPL-0006)