diff --git a/.github/workflows/agent-performance-analyzer.lock.yml b/.github/workflows/agent-performance-analyzer.lock.yml index 18de20145d0..95259331ee4 100644 --- a/.github/workflows/agent-performance-analyzer.lock.yml +++ b/.github/workflows/agent-performance-analyzer.lock.yml @@ -31,7 +31,7 @@ name: "Agent Performance Analyzer - Meta-Orchestrator" "on": schedule: - - cron: "43 20 * * *" + - cron: "43 3 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/agent-persona-explorer.lock.yml b/.github/workflows/agent-persona-explorer.lock.yml index e141223fde8..782e2c63871 100644 --- a/.github/workflows/agent-persona-explorer.lock.yml +++ b/.github/workflows/agent-persona-explorer.lock.yml @@ -31,7 +31,7 @@ name: "Agent Persona Explorer" "on": schedule: - - cron: "26 15 * * *" + - cron: "37 2 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/audit-workflows.lock.yml b/.github/workflows/audit-workflows.lock.yml index 89ca863fdb1..84b51f638bc 100644 --- a/.github/workflows/audit-workflows.lock.yml +++ b/.github/workflows/audit-workflows.lock.yml @@ -33,7 +33,7 @@ name: "Agentic Workflow Audit Agent" "on": schedule: - - cron: "12 16 * * *" + - cron: "48 20 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/claude-code-user-docs-review.lock.yml b/.github/workflows/claude-code-user-docs-review.lock.yml index affa0f2f1da..9bd8698da4a 100644 --- a/.github/workflows/claude-code-user-docs-review.lock.yml +++ b/.github/workflows/claude-code-user-docs-review.lock.yml @@ -31,7 +31,7 @@ name: "Claude Code User Documentation Review" "on": schedule: - - cron: "13 17 * * *" + - cron: "48 12 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/cli-version-checker.lock.yml b/.github/workflows/cli-version-checker.lock.yml index c48018ed58b..2e01a21f8db 100644 --- a/.github/workflows/cli-version-checker.lock.yml +++ b/.github/workflows/cli-version-checker.lock.yml @@ -32,7 +32,7 @@ name: "CLI Version Checker" "on": schedule: - - cron: "45 6 * * *" + - cron: "23 3 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/code-simplifier.lock.yml b/.github/workflows/code-simplifier.lock.yml index e9678079a5e..a3c86d531c9 100644 --- a/.github/workflows/code-simplifier.lock.yml +++ b/.github/workflows/code-simplifier.lock.yml @@ -32,7 +32,7 @@ name: "Code Simplifier" "on": schedule: - - cron: "30 17 * * *" + - cron: "37 5 * * *" # Friendly format: daily (scattered) # skip-if-match: is:pr is:open in:title "[code-simplifier]" # Skip-if-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/constraint-solving-potd.lock.yml b/.github/workflows/constraint-solving-potd.lock.yml index 4efad1be591..a864e2aba93 100644 --- a/.github/workflows/constraint-solving-potd.lock.yml +++ b/.github/workflows/constraint-solving-potd.lock.yml @@ -26,7 +26,7 @@ name: "Constraint Solving — Problem of the Day" "on": schedule: - - cron: "32 16 * * *" + - cron: "31 12 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/copilot-agent-analysis.lock.yml b/.github/workflows/copilot-agent-analysis.lock.yml index 2c54429ee09..dc6f015b763 100644 --- a/.github/workflows/copilot-agent-analysis.lock.yml +++ b/.github/workflows/copilot-agent-analysis.lock.yml @@ -34,7 +34,7 @@ name: "Copilot Agent PR Analysis" "on": schedule: - - cron: "9 10 * * *" + - cron: "19 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/copilot-cli-deep-research.lock.yml b/.github/workflows/copilot-cli-deep-research.lock.yml index 70cbc00f9ed..b7073332755 100644 --- a/.github/workflows/copilot-cli-deep-research.lock.yml +++ b/.github/workflows/copilot-cli-deep-research.lock.yml @@ -31,7 +31,7 @@ name: "Copilot CLI Deep Research Agent" "on": schedule: - - cron: "50 10 * * *" + - cron: "52 20 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/copilot-pr-prompt-analysis.lock.yml b/.github/workflows/copilot-pr-prompt-analysis.lock.yml index a6e6e46897f..ba42b66c295 100644 --- a/.github/workflows/copilot-pr-prompt-analysis.lock.yml +++ b/.github/workflows/copilot-pr-prompt-analysis.lock.yml @@ -34,7 +34,7 @@ name: "Copilot PR Prompt Pattern Analysis" "on": schedule: - - cron: "11 10 * * *" + - cron: "7 4 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/copilot-session-insights.lock.yml b/.github/workflows/copilot-session-insights.lock.yml index 803005e27fb..c10f91d5f78 100644 --- a/.github/workflows/copilot-session-insights.lock.yml +++ b/.github/workflows/copilot-session-insights.lock.yml @@ -36,7 +36,7 @@ name: "Copilot Session Insights" "on": schedule: - - cron: "47 21 * * *" + - cron: "14 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-assign-issue-to-user.lock.yml b/.github/workflows/daily-assign-issue-to-user.lock.yml index be549a2dfc5..cd755212f3b 100644 --- a/.github/workflows/daily-assign-issue-to-user.lock.yml +++ b/.github/workflows/daily-assign-issue-to-user.lock.yml @@ -26,7 +26,7 @@ name: "Auto-Assign Issue" "on": schedule: - - cron: "5 14 * * *" + - cron: "25 23 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-cli-performance.lock.yml b/.github/workflows/daily-cli-performance.lock.yml index bb03d94414a..d2d1abdabc6 100644 --- a/.github/workflows/daily-cli-performance.lock.yml +++ b/.github/workflows/daily-cli-performance.lock.yml @@ -34,7 +34,7 @@ name: "Daily CLI Performance Agent" # permissions: # Permissions applied to pre-activation job # contents: read schedule: - - cron: "5 22 * * *" + - cron: "7 5 * * *" # Friendly format: daily (scattered) # steps: # Steps injected into pre-activation job # - id: changes diff --git a/.github/workflows/daily-cli-tools-tester.lock.yml b/.github/workflows/daily-cli-tools-tester.lock.yml index 30d3b72fd6b..cdd96b94941 100644 --- a/.github/workflows/daily-cli-tools-tester.lock.yml +++ b/.github/workflows/daily-cli-tools-tester.lock.yml @@ -31,7 +31,7 @@ name: "Daily CLI Tools Exploratory Tester" "on": schedule: - - cron: "24 4 * * *" + - cron: "37 4 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-code-metrics.lock.yml b/.github/workflows/daily-code-metrics.lock.yml index 1234e967af9..1f74210089a 100644 --- a/.github/workflows/daily-code-metrics.lock.yml +++ b/.github/workflows/daily-code-metrics.lock.yml @@ -33,7 +33,7 @@ name: "Daily Code Metrics and Trend Tracking Agent" "on": schedule: - - cron: "29 18 * * *" + - cron: "19 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-community-attribution.lock.yml b/.github/workflows/daily-community-attribution.lock.yml index 233ffd7b32e..cfb59168f50 100644 --- a/.github/workflows/daily-community-attribution.lock.yml +++ b/.github/workflows/daily-community-attribution.lock.yml @@ -31,7 +31,7 @@ name: "Daily Community Attribution Updater" "on": schedule: - - cron: "37 6 * * *" + - cron: "27 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-compiler-quality.lock.yml b/.github/workflows/daily-compiler-quality.lock.yml index 0487b91059a..e99edf24c60 100644 --- a/.github/workflows/daily-compiler-quality.lock.yml +++ b/.github/workflows/daily-compiler-quality.lock.yml @@ -32,7 +32,7 @@ name: "Daily Compiler Quality Check" "on": schedule: - - cron: "5 0 * * *" + - cron: "23 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-doc-healer.lock.yml b/.github/workflows/daily-doc-healer.lock.yml index 7da059dbb5e..5394f399307 100644 --- a/.github/workflows/daily-doc-healer.lock.yml +++ b/.github/workflows/daily-doc-healer.lock.yml @@ -32,7 +32,7 @@ name: "Daily Documentation Healer" "on": schedule: - - cron: "24 8 * * *" + - cron: "51 12 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index 03aa099d099..e39cc51d977 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -31,7 +31,7 @@ name: "Daily Documentation Updater" "on": schedule: - - cron: "38 18 * * *" + - cron: "14 20 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-firewall-report.lock.yml b/.github/workflows/daily-firewall-report.lock.yml index 8e4bfc06a2f..59a917a37e0 100644 --- a/.github/workflows/daily-firewall-report.lock.yml +++ b/.github/workflows/daily-firewall-report.lock.yml @@ -32,7 +32,7 @@ name: "Daily Firewall Logs Collector and Reporter" "on": schedule: - - cron: "37 0 * * *" + - cron: "30 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-function-namer.lock.yml b/.github/workflows/daily-function-namer.lock.yml index 416901792ff..90c75cbe511 100644 --- a/.github/workflows/daily-function-namer.lock.yml +++ b/.github/workflows/daily-function-namer.lock.yml @@ -32,7 +32,7 @@ name: "Daily Go Function Namer" "on": schedule: - - cron: "12 5 * * *" + - cron: "53 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-integrity-analysis.lock.yml b/.github/workflows/daily-integrity-analysis.lock.yml index 8c3e2cc7bb8..ad4ec3416b5 100644 --- a/.github/workflows/daily-integrity-analysis.lock.yml +++ b/.github/workflows/daily-integrity-analysis.lock.yml @@ -32,7 +32,7 @@ name: "Daily DIFC Integrity-Filtered Events Analyzer" "on": schedule: - - cron: "54 9 * * *" + - cron: "54 19 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index 81a52335c7f..5ca932cc3f7 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -36,7 +36,7 @@ name: "Daily Issues Report Generator" "on": schedule: - - cron: "45 20 * * *" + - cron: "6 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-malicious-code-scan.lock.yml b/.github/workflows/daily-malicious-code-scan.lock.yml index 8ba2548391a..7a75ed4c251 100644 --- a/.github/workflows/daily-malicious-code-scan.lock.yml +++ b/.github/workflows/daily-malicious-code-scan.lock.yml @@ -31,7 +31,7 @@ name: "Daily Malicious Code Scan Agent" "on": schedule: - - cron: "24 9 * * *" + - cron: "8 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index ec89428f9c5..ed47db26567 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -32,7 +32,7 @@ name: "Multi-Device Docs Tester" "on": schedule: - - cron: "6 2 * * *" + - cron: "52 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-observability-report.lock.yml b/.github/workflows/daily-observability-report.lock.yml index fb26990d752..7e0f3602594 100644 --- a/.github/workflows/daily-observability-report.lock.yml +++ b/.github/workflows/daily-observability-report.lock.yml @@ -31,7 +31,7 @@ name: "Daily Observability Report for AWF Firewall and MCP Gateway" "on": schedule: - - cron: "14 4 * * *" + - cron: "30 23 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-performance-summary.lock.yml b/.github/workflows/daily-performance-summary.lock.yml index 38b077c0270..93620f4c110 100644 --- a/.github/workflows/daily-performance-summary.lock.yml +++ b/.github/workflows/daily-performance-summary.lock.yml @@ -33,7 +33,7 @@ name: "Daily Project Performance Summary Generator (Using MCP Scripts)" "on": schedule: - - cron: "51 8 * * *" + - cron: "13 23 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-regulatory.lock.yml b/.github/workflows/daily-regulatory.lock.yml index 7987ef30fce..9f0dfb41a16 100644 --- a/.github/workflows/daily-regulatory.lock.yml +++ b/.github/workflows/daily-regulatory.lock.yml @@ -32,7 +32,7 @@ name: "Daily Regulatory Report Generator" "on": schedule: - - cron: "52 23 * * *" + - cron: "54 20 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-rendering-scripts-verifier.lock.yml b/.github/workflows/daily-rendering-scripts-verifier.lock.yml index 8edefaee5e1..8eec21e9583 100644 --- a/.github/workflows/daily-rendering-scripts-verifier.lock.yml +++ b/.github/workflows/daily-rendering-scripts-verifier.lock.yml @@ -32,7 +32,7 @@ name: "Daily Rendering Scripts Verifier" "on": schedule: - - cron: "38 8 * * *" + - cron: "54 10 * * *" # Friendly format: daily (scattered) # skip-if-match: is:pr is:open in:title "[rendering-scripts]" # Skip-if-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/daily-safe-output-integrator.lock.yml b/.github/workflows/daily-safe-output-integrator.lock.yml index 908440041fd..bb0f7ed01f5 100644 --- a/.github/workflows/daily-safe-output-integrator.lock.yml +++ b/.github/workflows/daily-safe-output-integrator.lock.yml @@ -31,7 +31,7 @@ name: "Daily Safe Output Integrator" "on": schedule: - - cron: "19 5 * * *" + - cron: "49 22 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-safe-output-optimizer.lock.yml b/.github/workflows/daily-safe-output-optimizer.lock.yml index 73cfac87c1d..a4f81dde951 100644 --- a/.github/workflows/daily-safe-output-optimizer.lock.yml +++ b/.github/workflows/daily-safe-output-optimizer.lock.yml @@ -34,7 +34,7 @@ name: "Daily Safe Output Tool Optimizer" "on": schedule: - - cron: "11 4 * * *" + - cron: "31 23 * * *" # Friendly format: daily (scattered) # skip-if-match: is:issue is:open in:title "[safeoutputs]" # Skip-if-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/daily-safe-outputs-conformance.lock.yml b/.github/workflows/daily-safe-outputs-conformance.lock.yml index 5c244fbea85..9446259f296 100644 --- a/.github/workflows/daily-safe-outputs-conformance.lock.yml +++ b/.github/workflows/daily-safe-outputs-conformance.lock.yml @@ -31,7 +31,7 @@ name: "Daily Safe Outputs Conformance Checker" "on": schedule: - - cron: "16 15 * * *" + - cron: "48 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-secrets-analysis.lock.yml b/.github/workflows/daily-secrets-analysis.lock.yml index a8265955fce..ea7399f43c6 100644 --- a/.github/workflows/daily-secrets-analysis.lock.yml +++ b/.github/workflows/daily-secrets-analysis.lock.yml @@ -31,7 +31,7 @@ name: "Daily Secrets Analysis Agent" "on": schedule: - - cron: "38 4 * * *" + - cron: "6 22 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-security-red-team.lock.yml b/.github/workflows/daily-security-red-team.lock.yml index fe8ad7ad584..5da133d6031 100644 --- a/.github/workflows/daily-security-red-team.lock.yml +++ b/.github/workflows/daily-security-red-team.lock.yml @@ -31,7 +31,7 @@ name: "Daily Security Red Team Agent" "on": schedule: - - cron: "34 12 * * *" + - cron: "19 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-semgrep-scan.lock.yml b/.github/workflows/daily-semgrep-scan.lock.yml index 606bee89818..44b88ba232b 100644 --- a/.github/workflows/daily-semgrep-scan.lock.yml +++ b/.github/workflows/daily-semgrep-scan.lock.yml @@ -31,7 +31,7 @@ name: "Daily Semgrep Scan" "on": schedule: - - cron: "11 18 * * *" + - cron: "29 23 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-syntax-error-quality.lock.yml b/.github/workflows/daily-syntax-error-quality.lock.yml index abe5f86f4e0..f813d0f121d 100644 --- a/.github/workflows/daily-syntax-error-quality.lock.yml +++ b/.github/workflows/daily-syntax-error-quality.lock.yml @@ -31,7 +31,7 @@ name: "Daily Syntax Error Quality Check" "on": schedule: - - cron: "38 11 * * *" + - cron: "19 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-team-evolution-insights.lock.yml b/.github/workflows/daily-team-evolution-insights.lock.yml index 6f84f96c99c..3a2cecf2566 100644 --- a/.github/workflows/daily-team-evolution-insights.lock.yml +++ b/.github/workflows/daily-team-evolution-insights.lock.yml @@ -31,7 +31,7 @@ name: "Daily Team Evolution Insights" "on": schedule: - - cron: "31 9 * * *" + - cron: "34 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/daily-testify-uber-super-expert.lock.yml b/.github/workflows/daily-testify-uber-super-expert.lock.yml index 08e70931285..cb1e1c7d881 100644 --- a/.github/workflows/daily-testify-uber-super-expert.lock.yml +++ b/.github/workflows/daily-testify-uber-super-expert.lock.yml @@ -34,7 +34,7 @@ name: "Daily Testify Uber Super Expert" "on": schedule: - - cron: "48 17 * * *" + - cron: "18 11 * * *" # Friendly format: daily (scattered) # skip-if-match: is:issue is:open in:title "[testify-expert]" # Skip-if-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/daily-workflow-updater.lock.yml b/.github/workflows/daily-workflow-updater.lock.yml index 16b564405fc..c0a41ce2327 100644 --- a/.github/workflows/daily-workflow-updater.lock.yml +++ b/.github/workflows/daily-workflow-updater.lock.yml @@ -27,7 +27,7 @@ name: "Daily Workflow Updater" "on": schedule: - - cron: "17 6 * * *" + - cron: "43 20 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/dead-code-remover.lock.yml b/.github/workflows/dead-code-remover.lock.yml index c7015363d80..720e40ecb01 100644 --- a/.github/workflows/dead-code-remover.lock.yml +++ b/.github/workflows/dead-code-remover.lock.yml @@ -31,7 +31,7 @@ name: "Dead Code Removal Agent" "on": schedule: - - cron: "25 21 * * *" + - cron: "42 11 * * *" # Friendly format: daily (scattered) # skip-if-match: is:pr is:open in:title "[dead-code] " # Skip-if-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/delight.lock.yml b/.github/workflows/delight.lock.yml index 9db2e20b2da..0603b7b3028 100644 --- a/.github/workflows/delight.lock.yml +++ b/.github/workflows/delight.lock.yml @@ -32,7 +32,7 @@ name: "Delight" "on": schedule: - - cron: "25 18 * * *" + - cron: "52 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/dependabot-burner.lock.yml b/.github/workflows/dependabot-burner.lock.yml index d57b2895c27..e5eba3c9d92 100644 --- a/.github/workflows/dependabot-burner.lock.yml +++ b/.github/workflows/dependabot-burner.lock.yml @@ -30,7 +30,7 @@ name: "Dependabot Burner" "on": schedule: - - cron: "27 4 * * 0" + - cron: "43 11 * * 5" # Friendly format: weekly (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index fc5a94387a5..cbc0ec88e3d 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -33,7 +33,7 @@ name: "Developer Documentation Consolidator" "on": schedule: - - cron: "34 4 * * *" + - cron: "52 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index bb1c55ac902..bf37c03c36f 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -32,7 +32,7 @@ name: "Documentation Noob Tester" "on": schedule: - - cron: "20 4 * * *" + - cron: "48 12 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/draft-pr-cleanup.lock.yml b/.github/workflows/draft-pr-cleanup.lock.yml index 03a9ad025b0..1ddcfed31b2 100644 --- a/.github/workflows/draft-pr-cleanup.lock.yml +++ b/.github/workflows/draft-pr-cleanup.lock.yml @@ -27,7 +27,7 @@ name: "Draft PR Cleanup" "on": schedule: - - cron: "31 19 * * *" + - cron: "51 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/duplicate-code-detector.lock.yml b/.github/workflows/duplicate-code-detector.lock.yml index 9b4d7a4ab1d..48736b05968 100644 --- a/.github/workflows/duplicate-code-detector.lock.yml +++ b/.github/workflows/duplicate-code-detector.lock.yml @@ -32,7 +32,7 @@ name: "Duplicate Code Detector" "on": schedule: - - cron: "44 3 * * *" + - cron: "41 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/firewall-escape.lock.yml b/.github/workflows/firewall-escape.lock.yml index 507e8251c83..b09a83692db 100644 --- a/.github/workflows/firewall-escape.lock.yml +++ b/.github/workflows/firewall-escape.lock.yml @@ -32,7 +32,7 @@ name: "The Great Escapi" types: - labeled schedule: - - cron: "42 15 * * *" + - cron: "53 5 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/github-remote-mcp-auth-test.lock.yml b/.github/workflows/github-remote-mcp-auth-test.lock.yml index 0071f4d18cf..7535ac05907 100644 --- a/.github/workflows/github-remote-mcp-auth-test.lock.yml +++ b/.github/workflows/github-remote-mcp-auth-test.lock.yml @@ -27,7 +27,7 @@ name: "GitHub Remote MCP Authentication Test" "on": schedule: - - cron: "5 20 * * *" + - cron: "23 3 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 95cac47a447..fe334493682 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -31,7 +31,7 @@ name: "Go Logger Enhancement" "on": schedule: - - cron: "29 14 * * *" + - cron: "5 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/gpclean.lock.yml b/.github/workflows/gpclean.lock.yml index e59e817fdc4..d960f29f4e6 100644 --- a/.github/workflows/gpclean.lock.yml +++ b/.github/workflows/gpclean.lock.yml @@ -31,7 +31,7 @@ name: "GPL Dependency Cleaner (gpclean)" "on": schedule: - - cron: "40 6 * * *" + - cron: "7 4 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index 252f94b4d1a..093b95690b0 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -27,7 +27,7 @@ name: "Instructions Janitor" "on": schedule: - - cron: "29 4 * * *" + - cron: "32 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/issue-arborist.lock.yml b/.github/workflows/issue-arborist.lock.yml index 9bafae1303b..04385972a56 100644 --- a/.github/workflows/issue-arborist.lock.yml +++ b/.github/workflows/issue-arborist.lock.yml @@ -32,7 +32,7 @@ name: "Issue Arborist" "on": schedule: - - cron: "54 7 * * *" + - cron: "30 21 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index c8f8c6d7efc..472a9fe6aca 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -27,7 +27,7 @@ name: "jsweep - JavaScript Unbloater" "on": schedule: - - cron: "40 21 * * *" + - cron: "37 3 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/lockfile-stats.lock.yml b/.github/workflows/lockfile-stats.lock.yml index c00aeb5ff92..f0e880c29e9 100644 --- a/.github/workflows/lockfile-stats.lock.yml +++ b/.github/workflows/lockfile-stats.lock.yml @@ -31,7 +31,7 @@ name: "Lockfile Statistics Analysis Agent" "on": schedule: - - cron: "21 5 * * *" + - cron: "53 23 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/metrics-collector.lock.yml b/.github/workflows/metrics-collector.lock.yml index 80e0d49d051..9aebf5a11f7 100644 --- a/.github/workflows/metrics-collector.lock.yml +++ b/.github/workflows/metrics-collector.lock.yml @@ -27,7 +27,7 @@ name: "Metrics Collector - Infrastructure Agent" "on": schedule: - - cron: "47 16 * * *" + - cron: "19 19 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/prompt-clustering-analysis.lock.yml b/.github/workflows/prompt-clustering-analysis.lock.yml index 94ac7b09482..d4ad12f8e74 100644 --- a/.github/workflows/prompt-clustering-analysis.lock.yml +++ b/.github/workflows/prompt-clustering-analysis.lock.yml @@ -35,7 +35,7 @@ name: "Copilot Agent Prompt Clustering Analysis" "on": schedule: - - cron: "40 14 * * *" + - cron: "46 19 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/safe-output-health.lock.yml b/.github/workflows/safe-output-health.lock.yml index 38240ce6d0a..1bde0a55bb1 100644 --- a/.github/workflows/safe-output-health.lock.yml +++ b/.github/workflows/safe-output-health.lock.yml @@ -33,7 +33,7 @@ name: "Safe Output Health Monitor" "on": schedule: - - cron: "32 23 * * *" + - cron: "47 12 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/schema-consistency-checker.lock.yml b/.github/workflows/schema-consistency-checker.lock.yml index 33dc1bab483..f568f4cdbb0 100644 --- a/.github/workflows/schema-consistency-checker.lock.yml +++ b/.github/workflows/schema-consistency-checker.lock.yml @@ -31,7 +31,7 @@ name: "Schema Consistency Checker" "on": schedule: - - cron: "24 16 * * *" + - cron: "37 3 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/semantic-function-refactor.lock.yml b/.github/workflows/semantic-function-refactor.lock.yml index 1eeadfd8856..bf59f2ad42b 100644 --- a/.github/workflows/semantic-function-refactor.lock.yml +++ b/.github/workflows/semantic-function-refactor.lock.yml @@ -32,7 +32,7 @@ name: "Semantic Function Refactoring" "on": schedule: - - cron: "44 10 * * *" + - cron: "12 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/sergo.lock.yml b/.github/workflows/sergo.lock.yml index 2662d2b9d08..3792baf46bb 100644 --- a/.github/workflows/sergo.lock.yml +++ b/.github/workflows/sergo.lock.yml @@ -32,7 +32,7 @@ name: "Sergo - Serena Go Expert" "on": schedule: - - cron: "8 12 * * *" + - cron: "6 20 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/static-analysis-report.lock.yml b/.github/workflows/static-analysis-report.lock.yml index 5c7522b28bd..5dbe6be80e6 100644 --- a/.github/workflows/static-analysis-report.lock.yml +++ b/.github/workflows/static-analysis-report.lock.yml @@ -31,7 +31,7 @@ name: "Static Analysis Report" "on": schedule: - - cron: "32 2 * * *" + - cron: "26 19 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/step-name-alignment.lock.yml b/.github/workflows/step-name-alignment.lock.yml index 1ee4cdd1a6e..5c6463949c9 100644 --- a/.github/workflows/step-name-alignment.lock.yml +++ b/.github/workflows/step-name-alignment.lock.yml @@ -27,7 +27,7 @@ name: "Step Name Alignment" "on": schedule: - - cron: "30 14 * * *" + - cron: "18 19 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/sub-issue-closer.lock.yml b/.github/workflows/sub-issue-closer.lock.yml index af14b802bfe..75b81090bae 100644 --- a/.github/workflows/sub-issue-closer.lock.yml +++ b/.github/workflows/sub-issue-closer.lock.yml @@ -27,7 +27,7 @@ name: "Sub-Issue Closer" "on": schedule: - - cron: "27 1 * * *" + - cron: "27 10 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/terminal-stylist.lock.yml b/.github/workflows/terminal-stylist.lock.yml index 1401e8ed255..650691bcc53 100644 --- a/.github/workflows/terminal-stylist.lock.yml +++ b/.github/workflows/terminal-stylist.lock.yml @@ -32,7 +32,7 @@ name: "Terminal Stylist" "on": schedule: - - cron: "9 17 * * *" + - cron: "39 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/ubuntu-image-analyzer.lock.yml b/.github/workflows/ubuntu-image-analyzer.lock.yml index 00e609b9185..d0399804b11 100644 --- a/.github/workflows/ubuntu-image-analyzer.lock.yml +++ b/.github/workflows/ubuntu-image-analyzer.lock.yml @@ -31,7 +31,7 @@ name: "Ubuntu Actions Image Analyzer" "on": schedule: - - cron: "50 13 * * 3" + - cron: "22 21 * * 4" # Friendly format: weekly (scattered) # skip-if-match: is:pr is:open in:title "[ubuntu-image]" # Skip-if-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/update-astro.lock.yml b/.github/workflows/update-astro.lock.yml index f00661519d0..0608db5f5cf 100644 --- a/.github/workflows/update-astro.lock.yml +++ b/.github/workflows/update-astro.lock.yml @@ -27,7 +27,7 @@ name: "Update Astro" "on": schedule: - - cron: "37 15 * * *" + - cron: "37 10 * * *" # Friendly format: daily (scattered) # skip-if-no-match: is:pr is:open author:app/dependabot label:dependencies # Skip-if-no-match processed as search check in pre-activation job workflow_dispatch: diff --git a/.github/workflows/weekly-blog-post-writer.lock.yml b/.github/workflows/weekly-blog-post-writer.lock.yml index 20dd737033b..237e82d0d9c 100644 --- a/.github/workflows/weekly-blog-post-writer.lock.yml +++ b/.github/workflows/weekly-blog-post-writer.lock.yml @@ -31,7 +31,7 @@ name: "Weekly Blog Post Writer" "on": schedule: - - cron: "25 20 * * 1" + - cron: "48 12 * * 1" # Friendly format: weekly on monday (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/weekly-editors-health-check.lock.yml b/.github/workflows/weekly-editors-health-check.lock.yml index 451fbe52393..2f8d770ca26 100644 --- a/.github/workflows/weekly-editors-health-check.lock.yml +++ b/.github/workflows/weekly-editors-health-check.lock.yml @@ -27,7 +27,7 @@ name: "Weekly Editors Health Check" "on": schedule: - - cron: "8 2 * * 5" + - cron: "42 12 * * 6" # Friendly format: weekly (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml index b182d94a922..7378b732fe4 100644 --- a/.github/workflows/weekly-safe-outputs-spec-review.lock.yml +++ b/.github/workflows/weekly-safe-outputs-spec-review.lock.yml @@ -27,7 +27,7 @@ name: "Weekly Safe Outputs Specification Review" "on": schedule: - - cron: "13 3 * * 1" + - cron: "46 10 * * 1" # Friendly format: weekly on monday (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/workflow-health-manager.lock.yml b/.github/workflows/workflow-health-manager.lock.yml index 43e206d2436..66461f9b423 100644 --- a/.github/workflows/workflow-health-manager.lock.yml +++ b/.github/workflows/workflow-health-manager.lock.yml @@ -31,7 +31,7 @@ name: "Workflow Health Manager - Meta-Orchestrator" "on": schedule: - - cron: "22 13 * * *" + - cron: "51 11 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/workflow-normalizer.lock.yml b/.github/workflows/workflow-normalizer.lock.yml index cb633ac83c7..155f53167ee 100644 --- a/.github/workflows/workflow-normalizer.lock.yml +++ b/.github/workflows/workflow-normalizer.lock.yml @@ -31,7 +31,7 @@ name: "Workflow Normalizer" "on": schedule: - - cron: "53 15 * * *" + - cron: "37 23 * * *" # Friendly format: daily (scattered) workflow_dispatch: inputs: diff --git a/.github/workflows/workflow-skill-extractor.lock.yml b/.github/workflows/workflow-skill-extractor.lock.yml index 49fea66a063..ce3aab9d228 100644 --- a/.github/workflows/workflow-skill-extractor.lock.yml +++ b/.github/workflows/workflow-skill-extractor.lock.yml @@ -31,7 +31,7 @@ name: "Workflow Skill Extractor" "on": schedule: - - cron: "48 4 * * 2" + - cron: "11 11 * * 4" # Friendly format: weekly (scattered) workflow_dispatch: inputs: diff --git a/docs/src/content/docs/reference/fuzzy-schedule-specification.md b/docs/src/content/docs/reference/fuzzy-schedule-specification.md index c919099f673..19ae213f999 100644 --- a/docs/src/content/docs/reference/fuzzy-schedule-specification.md +++ b/docs/src/content/docs/reference/fuzzy-schedule-specification.md @@ -7,7 +7,7 @@ sidebar: # Fuzzy Schedule Time Syntax Specification -**Version**: 1.1.0 +**Version**: 1.2.0 **Status**: Draft Specification **Latest Version**: [fuzzy-schedule-specification](/gh-aw/reference/fuzzy-schedule-specification/) **Editor**: GitHub Agentic Workflows Team @@ -547,18 +547,25 @@ This format ensures workflows with the same filename in different repositories r #### 6.3.1 Daily Schedule Scattering -For `FUZZY:DAILY * * *`: +For `FUZZY:DAILY * * *` and `FUZZY:DAILY_WEEKDAYS * * *`, an implementation MUST use the **weighted daily time slot pool** to select execution time: -1. Calculate hash modulo 1440 (24 hours * 60 minutes) -2. Convert result to hour and minute components -3. Generate cron: ` * * *` +1. Construct a weighted pool of (hour, minute) time slots using three preference tiers: + - **BEST** (weight 3): hours 02–05 UTC, odd minutes `{7, 13, 23, 37, 43, 53}` → 72 slots + - **GOOD** (weight 2): hours 10–12 UTC, minutes `[5, 54]` → 300 slots + - **OK** (weight 1): hours 19–23 UTC, minutes `[5, 54]` → 250 slots + - Total pool size: 622 slots +2. Select slot: `index = hash(workflow_identifier) % pool_size` +3. Extract `(hour, minute)` from the selected slot +4. Generate cron: ` * * *` (or `* * 1-5` for weekday variant) + +The pool is pre-computed once. Because each tier appears proportionally in the pool, a randomly selected slot is 3× more likely to land in the BEST window than in the OK window. **Example**: ``` -hash("github/gh-aw/workflow.md") % 1440 = 343 -hour = 343 / 60 = 5 -minute = 343 % 60 = 43 -cron = "43 5 * * *" (5:43 AM) +pool_size = 622 +hash("github/gh-aw/workflow.md") % 622 = 84 +slot[84] = (hour=2, minute=23) # BEST tier +cron = "23 2 * * *" (2:23 AM UTC) ``` #### 6.3.2 Daily Around Scattering @@ -641,45 +648,90 @@ cron = "53 */2 * * *" (runs at minute 53 every 2 hours) #### 6.3.5 Weekly Schedule Scattering -For `FUZZY:WEEKLY * * *`: - -1. Calculate hash modulo (7 * 24 * 60) = 10080 (week in minutes) -2. Extract day-of-week: `day = (hash_result / 1440) % 7` -3. Extract time: `time_in_minutes = hash_result % 1440` -4. Convert time to hour and minute -5. Generate cron: ` * * ` +For `FUZZY:WEEKLY * * *` and `FUZZY:WEEKLY:DOW * * *`: -For `FUZZY:WEEKLY:DOW * * DOW`: +1. Select day-of-week: `weekday = hash(workflow_identifier) % 7` (0=Sunday, 6=Saturday) + For `FUZZY:WEEKLY:DOW`, the day is fixed from the expression instead. +2. Select time from the **weighted daily time slot pool** (Section 6.3.1) +3. Generate cron: ` * * ` -1. Day is fixed from expression -2. Calculate hash modulo 1440 (day in minutes) -3. Convert to hour and minute -4. Generate cron: ` * * ` +Both patterns use the same weighted pool as the daily schedule, ensuring execution times prefer the BEST/GOOD/OK tiers rather than distributing flatly across the full day. **Example**: ``` weekly on monday day = 1 (Monday) -hash % 1440 = 343 -hour = 5, minute = 43 -cron = "43 5 * * 1" (Monday 5:43 AM) +pool selection → (hour=2, minute=23) # BEST tier +cron = "23 2 * * 1" (Monday 2:23 AM UTC) ``` #### 6.3.6 Bi-weekly and Tri-weekly Scattering -For `FUZZY:BI-WEEKLY * * *`: +For `FUZZY:BI-WEEKLY * * *` and `FUZZY:TRI-WEEKLY * * *`: + +1. Select time from the **weighted daily time slot pool** (Section 6.3.1) +2. Generate cron: ` */14 * *` (bi-weekly) or ` */21 * *` (tri-weekly) + +Both patterns use the same weighted pool to ensure execution during preferred low-traffic windows. + +### 6.4 Peak Minutes Avoidance + +To reduce scheduling collisions with other commonly-scheduled cron jobs, implementations MUST apply two minute-avoidance passes after computing the raw scattered minute value. + +#### 6.4.1 Hour Boundary Avoidance (`avoidHourBoundary`) + +Minutes near the hour boundary (0–4 and 55–59) are subject to elevated load on GitHub Actions infrastructure, especially at 00:00 UTC. + +An implementation MUST remap minute values as follows: + +| Input range | Output | +|-------------|--------| +| [0, 4] | minute + 5 | +| [55, 59] | minute − 5 | +| [5, 54] | unchanged | + +This ensures all generated minute values are in [5, 54]. -1. Calculate hash modulo 1440 -2. Convert to hour and minute -3. Generate cron: ` */14 * *` +**Scope**: Applied to ALL targeted-scatter patterns (DAILY_AROUND, DAILY_BETWEEN, WEEKLY_AROUND, and their weekday variants). -For `FUZZY:TRI-WEEKLY * * *`: +#### 6.4.2 Peak Minutes Avoidance (`avoidPeakMinutes`) -1. Calculate hash modulo 1440 -2. Convert to hour and minute -3. Generate cron: ` */21 * *` +Known high-traffic periods require avoidance of minutes that fall within ±3 of the peak minute values. -### 6.4 Algorithm Requirements +An implementation MUST apply the following remapping **after** `avoidHourBoundary`: + +| Condition | Avoid range | Replacement | +|-----------|-------------|-------------| +| hour ∈ [6, 9] AND minute ∈ [27, 33] | [27, 33] | 34 | +| hour ∈ [14, 18] AND minute ∈ [12, 18] | [12, 18] | 19 | +| hour ∈ [14, 18] AND minute ∈ [42, 48] | [42, 48] | 49 | + +**Rationale**: +- **EU morning peak** (06:00–09:59 UTC): `:30` is a commonly-used cron minute. Staying 3 minutes away (avoiding [27,33]) reduces collisions. +- **US business hours** (14:00–18:59 UTC): `:15` and `:45` are quarter-hour marks widely used by monitoring and reporting cron jobs. Staying 3 minutes away (avoiding [12,18] and [42,48]) reduces collisions. + +**Application order**: `avoidHourBoundary` MUST be applied before `avoidPeakMinutes`. + +**Scope**: `avoidPeakMinutes` applies only to targeted-scatter patterns. Full-day scatter patterns that use the weighted pool (Section 6.3.1) already avoid peak windows by construction, since the pool does not include EU peak hours (06–09) or US peak hours (14–18). + +**Example**: +``` +FUZZY:DAILY_AROUND:14:00, workflow "my-scanner" + Raw scattered time: 14:28 + Step 1 (avoidHourBoundary): 28 → 28 (no change; 28 ∈ [5,54]) + Step 2 (avoidPeakMinutes): 28 → 34 (shifted; hour ∈ [14,18], minute 28 ∈ [27,33] + — wait, hour=14, so EU rule doesn't apply; + US :15 rule: 28 ∉ [12,18]; :45 rule: 28 ∉ [42,48]) + → no shift needed; result: 14:28 + +FUZZY:DAILY_AROUND:15:00, workflow "my-monitor" + Raw scattered time: 15:13 + Step 1 (avoidHourBoundary): 13 → 13 (no change) + Step 2 (avoidPeakMinutes): 13 → 19 (shifted; hour ∈ [14,18], minute 13 ∈ [12,18]) + → result: 15:19 +``` + +### 6.5 Algorithm Requirements An implementation MUST ensure: @@ -687,6 +739,8 @@ An implementation MUST ensure: 2. Modulo operations use consistent integer division 3. Day wrapping uses consistent addition/subtraction rules 4. Minute and hour extraction uses consistent division and modulo operations +5. `avoidHourBoundary` is applied before `avoidPeakMinutes` for all targeted-scatter patterns +6. Full-day scatter patterns use the weighted daily time slot pool (Section 6.3.1) --- @@ -879,13 +933,19 @@ A conforming implementation MUST pass all Level 1 tests. Implementations claimin - **T-SCATTER-001**: Hash produces same output for same input - **T-SCATTER-002**: Different inputs produce different outputs - **T-SCATTER-003**: Hash value is within modulo range (0 to modulo-1) -- **T-SCATTER-004**: Daily schedule scatters across full 24-hour period +- **T-SCATTER-004**: Daily schedule selects time from weighted pool (BEST/GOOD/OK tiers only) - **T-SCATTER-005**: Around schedule stays within ±60 minute window - **T-SCATTER-006**: Between schedule stays within specified range - **T-SCATTER-007**: Midnight-crossing range handles day wrap correctly -- **T-SCATTER-008**: Hourly schedule produces minute 0-59 +- **T-SCATTER-008**: Hourly schedule produces minute in [5, 54] - **T-SCATTER-009**: Weekly schedule selects valid day 0-6 - **T-SCATTER-010**: Same workflow gets same time across compilations +- **T-SCATTER-011**: Daily schedule lands in BEST (02–05), GOOD (10–12), or OK (19–23) window +- **T-SCATTER-012**: Minute values in [5, 54] for all patterns (hour-boundary avoidance) +- **T-SCATTER-013**: DAILY_AROUND scatter landing in EU peak hours (06–09) avoids minutes [27, 33] +- **T-SCATTER-014**: DAILY_AROUND scatter landing in US business hours (14–18) avoids minutes [12, 18] and [42, 48] +- **T-SCATTER-015**: Weekly schedule uses weighted daily time pool (preferred windows) +- **T-SCATTER-016**: Bi-weekly and tri-weekly schedules use weighted daily time pool #### 9.2.7 Cron Generation Tests (Level 1-3) @@ -921,6 +981,10 @@ A conforming implementation MUST pass all Level 1 tests. Implementations claimin | Parse interval schedules | T-INTERVAL-001, 002 | 3 | Required | | Hash determinism | T-SCATTER-001, 002 | 1 | Required | | Scattering distribution | T-SCATTER-004-009 | 1-3 | Required | +| Weighted daily pool | T-SCATTER-011, 015, 016 | 1-3 | Required | +| Peak avoidance (hour boundary) | T-SCATTER-012 | 1-3 | Required | +| Peak avoidance (EU morning peak) | T-SCATTER-013 | 2-3 | Required | +| Peak avoidance (US business hours) | T-SCATTER-014 | 2-3 | Required | | Generate valid cron | T-CRON-001-006 | 1-3 | Required | ### 9.4 Test Execution @@ -1092,6 +1156,17 @@ Implementations MUST validate all user inputs before processing: ## Change Log +### Version 1.2.0 (Draft) + +- **Changed**: Section 6.3.1 — Replaced flat hash-modulo-1440 daily scatter with a **622-entry weighted daily time slot pool** (BEST 02–05 UTC ×3, GOOD 10–12 UTC ×2, OK 19–23 UTC ×1) +- **Changed**: Sections 6.3.5–6.3.6 — Weekly, bi-weekly, and tri-weekly scatter now uses the same weighted pool as the daily schedule +- **Added**: Section 6.4 — **Peak Minutes Avoidance** documenting: + - `avoidHourBoundary`: shifts minutes [0,4]→[5,9] and [55,59]→[50,54] + - `avoidPeakMinutes`: EU peak (hours 06–09) avoids ±3 min of :30 (shifts [27,33]→34); US business hours (14–18) avoids ±3 min of :15 (shifts [12,18]→19) and ±3 min of :45 (shifts [42,48]→49) +- **Renumbered**: Section 6.4 (Algorithm Requirements) → Section 6.5 +- **Added**: Compliance tests T-SCATTER-011 through T-SCATTER-016 covering weighted pool behaviour and peak avoidance +- **Updated**: Compliance checklist (Section 9.3) with new required rows for weighted pool and peak avoidance + ### Version 1.1.0 (Draft) - **Changed**: Hash function requirement relaxed from MUST to SHOULD for FNV-1a diff --git a/pkg/parser/schedule_fuzzy_scatter.go b/pkg/parser/schedule_fuzzy_scatter.go index 6f521a47dc9..52f39bcc5d9 100644 --- a/pkg/parser/schedule_fuzzy_scatter.go +++ b/pkg/parser/schedule_fuzzy_scatter.go @@ -14,6 +14,64 @@ var scheduleFuzzyScatterLog = logger.New("parser:schedule_fuzzy_scatter") // This file contains fuzzy schedule scattering logic that deterministically // distributes workflow execution times based on workflow identifiers. +// timeSlot represents a specific (hour, minute) pair used in the weighted daily pool. +type timeSlot struct { + hour int + minute int +} + +// bestDailyMinutes are the "odd" minutes preferred during the BEST tier (02:00–05:59 UTC). +// These low-traffic minutes reduce scheduling collisions with other cron jobs. +var bestDailyMinutes = []int{7, 13, 23, 37, 43, 53} + +// buildWeightedDailyPool constructs the weighted pool of (hour, minute) time slots +// used for full-day scatter patterns. The pool reflects the following distribution: +// +// - BEST (weight 3): 02:00–05:59 UTC at odd minutes (07,13,23,37,43,53) +// - GOOD (weight 2): 10:00–12:59 UTC (gap between EU/US peaks), minutes [5,54] +// - OK (weight 1): 19:00–23:59 UTC (evening hours), minutes [5,54] +// +// Using weights means a randomly selected slot is 3× more likely to land in the +// BEST window than the OK window. +func buildWeightedDailyPool() []timeSlot { + var pool []timeSlot + + // BEST: hours 02–05 at specified odd minutes, weight 3 (appear 3 times each) + for h := 2; h <= 5; h++ { + for _, m := range bestDailyMinutes { + pool = append(pool, timeSlot{h, m}, timeSlot{h, m}, timeSlot{h, m}) + } + } + + // GOOD: hours 10–12, all valid minutes [5,54], weight 2 (appear 2 times each) + for h := 10; h <= 12; h++ { + for m := 5; m <= 54; m++ { + pool = append(pool, timeSlot{h, m}, timeSlot{h, m}) + } + } + + // OK: hours 19–23, all valid minutes [5,54], weight 1 + for h := 19; h <= 23; h++ { + for m := 5; m <= 54; m++ { + pool = append(pool, timeSlot{h, m}) + } + } + + return pool +} + +// weightedDailyPool is the pre-computed weighted pool of daily time slots. +// Pool size: 4×6×3 (BEST) + 3×50×2 (GOOD) + 5×50×1 (OK) = 72 + 300 + 250 = 622 slots. +var weightedDailyPool = buildWeightedDailyPool() + +// weightedDailyTimeSlot returns a deterministic (hour, minute) pair sampled from the +// weighted daily time slot pool for the given workflow identifier. +// All returned slots are already within the preferred windows and have valid minutes. +func weightedDailyTimeSlot(identifier string) (int, int) { + slot := weightedDailyPool[stableHash(identifier, len(weightedDailyPool))] + return slot.hour, slot.minute +} + // avoidHourBoundary remaps a minute value to avoid the 5-minute window before // and after each hour (minutes 0–4 and 55–59). These windows are subject to // usage peaks on GitHub Actions, especially at 00:00 UTC. @@ -32,6 +90,33 @@ func avoidHourBoundary(minute int) int { return minute } +// avoidPeakMinutes shifts minute values that fall within 3 minutes of known high-traffic +// peak minutes during busy UTC hours: +// +// - EU morning peak (06:00–09:59 UTC): avoids minutes [27, 33] (±3 around :30), +// shifting any value in that window to 34 (first minute clearly outside the window) +// - US business hours (14:00–18:59 UTC): avoids minutes [12, 18] (±3 around :15) +// and [42, 48] (±3 around :45), shifting to 19 and 49 respectively +// +// All replacement values stay within [5, 54]. This is applied after avoidHourBoundary +// for targeted-scatter patterns where the hour is determined by a user-specified target. +func avoidPeakMinutes(hour, minute int) int { + // EU morning peak: stay 3 minutes away from :30 in hours 06–09 + if hour >= 6 && hour <= 9 && minute >= 27 && minute <= 33 { + return 34 + } + // US business hours (moderate): stay 3 minutes away from :15 and :45 in hours 14–18 + if hour >= 14 && hour <= 18 { + if minute >= 12 && minute <= 18 { + return 19 + } + if minute >= 42 && minute <= 48 { + return 49 + } + } + return minute +} + // stableHash returns a deterministic hash value in the range [0, modulo) // using FNV-1a hash algorithm, which is stable across platforms and Go versions. func stableHash(s string, modulo int) int { @@ -103,7 +188,7 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { } hour := scatteredMinutes / 60 - minute := avoidHourBoundary(scatteredMinutes % 60) + minute := avoidPeakMinutes(hour, avoidHourBoundary(scatteredMinutes%60)) result := fmt.Sprintf("%d %d * * 1-5", minute, hour) scheduleFuzzyScatterLog.Printf("FUZZY:DAILY_AROUND_WEEKDAYS scattered: original=%d:%d, scattered=%d:%d, result=%s", targetHour, targetMinute, hour, minute, result) @@ -172,7 +257,7 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { } hour := scatteredMinutes / 60 - minute := avoidHourBoundary(scatteredMinutes % 60) + minute := avoidPeakMinutes(hour, avoidHourBoundary(scatteredMinutes%60)) result := fmt.Sprintf("%d %d * * 1-5", minute, hour) scheduleFuzzyScatterLog.Printf("FUZZY:DAILY_BETWEEN_WEEKDAYS scattered: start=%d:%d, end=%d:%d, scattered=%d:%d, result=%s", startHour, startMinute, endHour, endMinute, hour, minute, result) @@ -229,7 +314,7 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { } hour := scatteredMinutes / 60 - minute := avoidHourBoundary(scatteredMinutes % 60) + minute := avoidPeakMinutes(hour, avoidHourBoundary(scatteredMinutes%60)) result := fmt.Sprintf("%d %d * * *", minute, hour) scheduleFuzzyScatterLog.Printf("FUZZY:DAILY_AROUND scattered: original=%d:%d, scattered=%d:%d, result=%s", targetHour, targetMinute, hour, minute, result) @@ -298,7 +383,7 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { } hour := scatteredMinutes / 60 - minute := avoidHourBoundary(scatteredMinutes % 60) + minute := avoidPeakMinutes(hour, avoidHourBoundary(scatteredMinutes%60)) result := fmt.Sprintf("%d %d * * *", minute, hour) scheduleFuzzyScatterLog.Printf("FUZZY:DAILY_BETWEEN scattered: start=%d:%d, end=%d:%d, scattered=%d:%d, result=%s", startHour, startMinute, endHour, endMinute, hour, minute, result) @@ -306,32 +391,22 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { return result, nil } - // For FUZZY:DAILY_WEEKDAYS * * *, we scatter across 24 hours on weekdays only + // For FUZZY:DAILY_WEEKDAYS * * *, scatter across the preferred daily time windows on weekdays if strings.HasPrefix(fuzzyCron, "FUZZY:DAILY_WEEKDAYS") { - // Use 24*50 slots (50 valid minutes per hour, avoiding the 5-minute - // window around each hour boundary) to get a deterministic time. - hash := stableHash(workflowIdentifier, 24*50) - - hour := hash / 50 - minute := (hash % 50) + 5 // minutes in [5, 54] + hour, minute := weightedDailyTimeSlot(workflowIdentifier) result := fmt.Sprintf("%d %d * * 1-5", minute, hour) - scheduleFuzzyScatterLog.Printf("FUZZY:DAILY_WEEKDAYS scattered: hash=%d, result=%s", hash, result) + scheduleFuzzyScatterLog.Printf("FUZZY:DAILY_WEEKDAYS scattered: result=%s", result) // Return scattered daily cron with weekday restriction: minute hour * * 1-5 return result, nil } - // For FUZZY:DAILY * * *, we scatter across 24 hours + // For FUZZY:DAILY * * *, scatter across the preferred daily time windows if strings.HasPrefix(fuzzyCron, "FUZZY:DAILY") { - // Use 24*50 slots (50 valid minutes per hour, avoiding the 5-minute - // window around each hour boundary) to get a deterministic time. - hash := stableHash(workflowIdentifier, 24*50) - - hour := hash / 50 - minute := (hash % 50) + 5 // minutes in [5, 54] + hour, minute := weightedDailyTimeSlot(workflowIdentifier) result := fmt.Sprintf("%d %d * * *", minute, hour) - scheduleFuzzyScatterLog.Printf("FUZZY:DAILY scattered: hash=%d, result=%s", hash, result) + scheduleFuzzyScatterLog.Printf("FUZZY:DAILY scattered: result=%s", result) // Return scattered daily cron: minute hour * * * return result, nil } @@ -436,7 +511,7 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { } hour := scatteredMinutes / 60 - minute := avoidHourBoundary(scatteredMinutes % 60) + minute := avoidPeakMinutes(hour, avoidHourBoundary(scatteredMinutes%60)) result := fmt.Sprintf("%d %d * * %s", minute, hour, weekday) scheduleFuzzyScatterLog.Printf("FUZZY:WEEKLY_AROUND scattered: weekday=%s, target=%d:%d, scattered=%d:%d, result=%s", weekday, targetHour, targetMinute, hour, minute, result) @@ -455,30 +530,19 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { weekdayPart := strings.TrimPrefix(parts[0], "FUZZY:WEEKLY:") weekday := weekdayPart - // Use 24*50 slots (50 valid minutes per hour, avoiding the 5-minute - // window around each hour boundary) to get a deterministic time. - hash := stableHash(workflowIdentifier, 24*50) - - hour := hash / 50 - minute := (hash % 50) + 5 // minutes in [5, 54] + hour, minute := weightedDailyTimeSlot(workflowIdentifier) result := fmt.Sprintf("%d %d * * %s", minute, hour, weekday) - scheduleFuzzyScatterLog.Printf("FUZZY:WEEKLY:%s scattered: hash=%d, result=%s", weekday, hash, result) + scheduleFuzzyScatterLog.Printf("FUZZY:WEEKLY:%s scattered: result=%s", weekday, result) // Return scattered weekly cron: minute hour * * DOW return result, nil } - // For FUZZY:WEEKLY * * *, we scatter across all weekdays and times + // For FUZZY:WEEKLY * * *, scatter the weekday deterministically and pick a + // preferred time from the weighted daily pool. if strings.HasPrefix(fuzzyCron, "FUZZY:WEEKLY") { - // Use 7 * 24 * 50 slots (50 valid minutes per hour, avoiding the 5-minute - // window around each hour boundary) to get a deterministic weekday and time. - hash := stableHash(workflowIdentifier, 7*24*50) - - // Each "day block" contains 24*50 = 1200 slots. - weekday := hash / (24 * 50) // Which day of the week (0-6) - slotInDay := hash % (24 * 50) // Which slot of that day (0-1199) - hour := slotInDay / 50 - minute := (slotInDay % 50) + 5 // minutes in [5, 54] + weekday := stableHash(workflowIdentifier, 7) // Which day of the week (0-6) + hour, minute := weightedDailyTimeSlot(workflowIdentifier) result := fmt.Sprintf("%d %d * * %d", minute, hour, weekday) scheduleFuzzyScatterLog.Printf("FUZZY:WEEKLY scattered: weekday=%d, time=%d:%d, result=%s", weekday, hour, minute, result) @@ -486,16 +550,9 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { return result, nil } - // For FUZZY:BI_WEEKLY * * *, we scatter across 2 weeks (14 days) + // For FUZZY:BI_WEEKLY * * *, schedule every 14 days at a preferred time if strings.HasPrefix(fuzzyCron, "FUZZY:BI_WEEKLY") { - // Use 14 * 24 * 50 slots (50 valid minutes per hour, avoiding the 5-minute - // window around each hour boundary) to get a deterministic time. - hash := stableHash(workflowIdentifier, 14*24*50) - - // Extract time within a day using 50-slot per hour mapping. - slotInDay := hash % (24 * 50) // Which slot of the day (0-1199) - hour := slotInDay / 50 - minute := (slotInDay % 50) + 5 // minutes in [5, 54] + hour, minute := weightedDailyTimeSlot(workflowIdentifier) result := fmt.Sprintf("%d %d */%d * *", minute, hour, 14) scheduleFuzzyScatterLog.Printf("FUZZY:BI_WEEKLY scattered: time=%d:%d, result=%s", hour, minute, result) @@ -504,16 +561,9 @@ func ScatterSchedule(fuzzyCron, workflowIdentifier string) (string, error) { return result, nil } - // For FUZZY:TRI_WEEKLY * * *, we scatter across 3 weeks (21 days) + // For FUZZY:TRI_WEEKLY * * *, schedule every 21 days at a preferred time if strings.HasPrefix(fuzzyCron, "FUZZY:TRI_WEEKLY") { - // Use 21 * 24 * 50 slots (50 valid minutes per hour, avoiding the 5-minute - // window around each hour boundary) to get a deterministic time. - hash := stableHash(workflowIdentifier, 21*24*50) - - // Extract time within a day using 50-slot per hour mapping. - slotInDay := hash % (24 * 50) // Which slot of the day (0-1199) - hour := slotInDay / 50 - minute := (slotInDay % 50) + 5 // minutes in [5, 54] + hour, minute := weightedDailyTimeSlot(workflowIdentifier) result := fmt.Sprintf("%d %d */%d * *", minute, hour, 21) scheduleFuzzyScatterLog.Printf("FUZZY:TRI_WEEKLY scattered: time=%d:%d, result=%s", hour, minute, result) diff --git a/pkg/parser/schedule_fuzzy_scatter_test.go b/pkg/parser/schedule_fuzzy_scatter_test.go index b498c30f740..fe5d9f2586b 100644 --- a/pkg/parser/schedule_fuzzy_scatter_test.go +++ b/pkg/parser/schedule_fuzzy_scatter_test.go @@ -690,3 +690,174 @@ func TestScatterScheduleAvoidsHourBoundary(t *testing.T) { } } } + +// TestScatterScheduleAvoidsEUMorningPeak verifies that targeted-scatter patterns +// never produce a minute within 3 of :30 (i.e. minutes 27–33) during EU morning +// peak hours (06:00–09:59 UTC). +func TestScatterScheduleAvoidsEUMorningPeak(t *testing.T) { + workflowIDs := []string{ + "workflow-a.md", "workflow-b.md", "workflow-c.md", + "test-workflow", "daily-security-scan", "weekly-report", + "my-org/my-repo/my-workflow.md", "scanner-job", + } + + // Targeted patterns that scatter around EU morning peak hours. + patterns := []string{ + "FUZZY:DAILY_AROUND:7:0 * * *", + "FUZZY:DAILY_AROUND:7:30 * * *", + "FUZZY:DAILY_AROUND:8:0 * * *", + "FUZZY:DAILY_AROUND:9:0 * * *", + "FUZZY:DAILY_AROUND_WEEKDAYS:7:0 * * *", + "FUZZY:DAILY_AROUND_WEEKDAYS:8:30 * * *", + "FUZZY:DAILY_BETWEEN:6:0:10:0 * * *", + "FUZZY:DAILY_BETWEEN_WEEKDAYS:6:0:10:0 * * *", + "FUZZY:WEEKLY_AROUND:1:7:0 * * *", + "FUZZY:WEEKLY_AROUND:3:8:30 * * *", + } + + for _, pattern := range patterns { + for _, wfID := range workflowIDs { + t.Run(pattern+"/"+wfID, func(t *testing.T) { + result, err := ScatterSchedule(pattern, wfID) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + fields := strings.Fields(result) + if len(fields) != 5 { + t.Fatalf("expected 5 cron fields, got %d: %s", len(fields), result) + } + + var minute, hour int + if _, scanErr := fmt.Sscanf(fields[0], "%d", &minute); scanErr != nil { + t.Fatalf("could not parse minute from cron %q: %v", result, scanErr) + } + if _, scanErr := fmt.Sscanf(fields[1], "%d", &hour); scanErr != nil { + // Hour field may be a range or wildcard for some patterns – skip check. + return + } + + // Must stay 3 minutes away from :30 in hours 06-09 + if hour >= 6 && hour <= 9 && minute >= 27 && minute <= 33 { + t.Errorf("pattern=%q wfID=%q: cron %q schedules at :%02d during EU morning peak (must stay 3 min from :30 in hours 06-09 UTC)", + pattern, wfID, result, minute) + } + }) + } + } +} + +// TestScatterScheduleAvoidsUSBusinessHours verifies that targeted-scatter patterns +// never produce a minute within 3 of :15 or :45 (i.e. [12,18] or [42,48]) during +// US business hours (14:00–18:59 UTC). +func TestScatterScheduleAvoidsUSBusinessHours(t *testing.T) { + workflowIDs := []string{ + "workflow-a.md", "workflow-b.md", "workflow-c.md", + "test-workflow", "daily-security-scan", "weekly-report", + "my-org/my-repo/my-workflow.md", "scanner-job", + } + + // Targeted patterns that scatter around US business hours. + patterns := []string{ + "FUZZY:DAILY_AROUND:14:0 * * *", + "FUZZY:DAILY_AROUND:15:15 * * *", + "FUZZY:DAILY_AROUND:16:45 * * *", + "FUZZY:DAILY_AROUND:17:0 * * *", + "FUZZY:DAILY_AROUND:18:0 * * *", + "FUZZY:DAILY_AROUND_WEEKDAYS:15:0 * * *", + "FUZZY:DAILY_AROUND_WEEKDAYS:16:45 * * *", + "FUZZY:DAILY_BETWEEN:14:0:19:0 * * *", + "FUZZY:DAILY_BETWEEN_WEEKDAYS:14:0:19:0 * * *", + "FUZZY:WEEKLY_AROUND:2:15:15 * * *", + "FUZZY:WEEKLY_AROUND:4:17:0 * * *", + } + + for _, pattern := range patterns { + for _, wfID := range workflowIDs { + t.Run(pattern+"/"+wfID, func(t *testing.T) { + result, err := ScatterSchedule(pattern, wfID) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + fields := strings.Fields(result) + if len(fields) != 5 { + t.Fatalf("expected 5 cron fields, got %d: %s", len(fields), result) + } + + var minute, hour int + if _, scanErr := fmt.Sscanf(fields[0], "%d", &minute); scanErr != nil { + t.Fatalf("could not parse minute from cron %q: %v", result, scanErr) + } + if _, scanErr := fmt.Sscanf(fields[1], "%d", &hour); scanErr != nil { + // Hour field may be a range or wildcard for some patterns – skip check. + return + } + + // Must stay 3 minutes away from :15 and :45 in hours 14-18 + if hour >= 14 && hour <= 18 { + if minute >= 12 && minute <= 18 { + t.Errorf("pattern=%q wfID=%q: cron %q schedules at :%02d during US business hours (must stay 3 min from :15 in hours 14-18 UTC)", + pattern, wfID, result, minute) + } + if minute >= 42 && minute <= 48 { + t.Errorf("pattern=%q wfID=%q: cron %q schedules at :%02d during US business hours (must stay 3 min from :45 in hours 14-18 UTC)", + pattern, wfID, result, minute) + } + } + }) + } + } +} + +// TestScatterScheduleUsesPreferredWindows verifies that full-day scatter patterns +// (FUZZY:DAILY, FUZZY:DAILY_WEEKDAYS, FUZZY:WEEKLY, etc.) land exclusively in the +// preferred time windows: BEST (02–05 UTC), GOOD (10–12 UTC), or OK (19–23 UTC). +func TestScatterScheduleUsesPreferredWindows(t *testing.T) { + workflowIDs := []string{ + "workflow-a.md", "workflow-b.md", "workflow-c.md", + "test-workflow", "daily-security-scan", "weekly-report", + "hourly-checker", "my-org/my-repo/my-workflow.md", + "alpha", "beta", "gamma", "delta", "epsilon", + } + + patterns := []string{ + "FUZZY:DAILY * * *", + "FUZZY:DAILY_WEEKDAYS * * *", + "FUZZY:WEEKLY * * *", + "FUZZY:WEEKLY:1 * * *", + "FUZZY:WEEKLY:5 * * *", + "FUZZY:BI_WEEKLY * * *", + "FUZZY:TRI_WEEKLY * * *", + } + + isInPreferredWindow := func(hour int) bool { + return (hour >= 2 && hour <= 5) || (hour >= 10 && hour <= 12) || (hour >= 19 && hour <= 23) + } + + for _, pattern := range patterns { + for _, wfID := range workflowIDs { + t.Run(pattern+"/"+wfID, func(t *testing.T) { + result, err := ScatterSchedule(pattern, wfID) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + fields := strings.Fields(result) + if len(fields) != 5 { + t.Fatalf("expected 5 cron fields, got %d: %s", len(fields), result) + } + + var hour int + if _, scanErr := fmt.Sscanf(fields[1], "%d", &hour); scanErr != nil { + t.Fatalf("could not parse hour from cron %q: %v", result, scanErr) + } + + if !isInPreferredWindow(hour) { + t.Errorf("pattern=%q wfID=%q: cron %q schedules at hour %d, which is not in a preferred window (02-05, 10-12, or 19-23 UTC)", + pattern, wfID, result, hour) + } + }) + } + } +} diff --git a/pkg/parser/schedule_parser_stability_test.go b/pkg/parser/schedule_parser_stability_test.go index 341ab5e8061..394c44833e8 100644 --- a/pkg/parser/schedule_parser_stability_test.go +++ b/pkg/parser/schedule_parser_stability_test.go @@ -57,13 +57,13 @@ func TestScatterScheduleCrossPlatformConsistency(t *testing.T) { name: "daily - workflow-a.md", fuzzyCron: "FUZZY:DAILY * * *", workflowIdentifier: "workflow-a.md", - expectedCron: "46 14 * * *", + expectedCron: "23 2 * * *", }, { name: "daily - workflow-b.md", fuzzyCron: "FUZZY:DAILY * * *", workflowIdentifier: "workflow-b.md", - expectedCron: "37 1 * * *", + expectedCron: "41 22 * * *", }, { name: "hourly/1 - workflow-a.md", @@ -81,13 +81,13 @@ func TestScatterScheduleCrossPlatformConsistency(t *testing.T) { name: "weekly - workflow-a.md", fuzzyCron: "FUZZY:WEEKLY * * *", workflowIdentifier: "workflow-a.md", - expectedCron: "46 14 * * 0", + expectedCron: "23 2 * * 6", }, { name: "weekly:1 - workflow-a.md", fuzzyCron: "FUZZY:WEEKLY:1 * * *", workflowIdentifier: "workflow-a.md", - expectedCron: "46 14 * * 1", + expectedCron: "23 2 * * 1", }, { name: "daily around 14:00 - workflow-a.md",