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
188 changes: 188 additions & 0 deletions .github/workflows/maint-46-post-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
name: Maint 46 Post CI

'on':
workflow_run:
workflows:
- Gate
types:
- completed

permissions:
actions: read
contents: read
issues: write
pull-requests: write
statuses: write

concurrency:
group: maint-46-post-ci-${{ github.event.workflow_run.id || github.run_id }}
cancel-in-progress: false

jobs:
summary:
name: post ci summary
if: ${{ github.event.workflow_run.event == 'pull_request' }}
runs-on: ubuntu-latest
env:
ARTIFACT_ROOT: summary_artifacts
steps:
- name: Checkout helpers
uses: actions/checkout@v4
with:
sparse-checkout: |
.github/scripts
tools/post_ci_summary.py
sparse-checkout-cone-mode: false

- name: Validate workflow syntax
uses: rhysd/actionlint@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}

- name: Discover Gate workflow runs
id: discover
uses: actions/github-script@v7
with:
script: |
const { discoverWorkflowRuns } = require('./.github/scripts/maint-post-ci.js');
await discoverWorkflowRuns({ github, context, core });

- name: Download Gate artifacts
if: ${{ steps.discover.outputs.gate_run_id != '' }}
uses: actions/download-artifact@v4
with:
pattern: gate-*
merge-multiple: true
path: ${{ env.ARTIFACT_ROOT }}/downloads
run-id: ${{ steps.discover.outputs.gate_run_id }}
github-token: ${{ secrets.GITHUB_TOKEN }}

- name: Collect coverage payloads
if: ${{ steps.discover.outputs.gate_run_id != '' }}
id: coverage_payloads
run: |
python - <<'PY'
import json
import os
from pathlib import Path

artifact_root = Path(os.environ.get('ARTIFACT_ROOT', 'summary_artifacts')) / 'downloads'
stats_text = ''
delta_text = ''
coverage_section = ''

def find_file(name: str) -> Path | None:
candidates = sorted(artifact_root.rglob(name))
for candidate in candidates:
if candidate.is_file():
return candidate
return None

stats_path = find_file('gate-coverage.json')
if stats_path:
stats_text = stats_path.read_text(encoding='utf-8')

delta_path = find_file('gate-coverage-delta.json')
if delta_path:
delta_text = delta_path.read_text(encoding='utf-8')

summary_path = find_file('gate-coverage-summary.md')
if summary_path:
coverage_section = summary_path.read_text(encoding='utf-8')

output_path = Path(os.environ['GITHUB_OUTPUT'])
with output_path.open('a', encoding='utf-8') as handle:
if stats_text:
handle.write('stats_json<<EOF\n')
handle.write(stats_text)
if not stats_text.endswith('\n'):
handle.write('\n')
handle.write('EOF\n')
if delta_text:
handle.write('delta_json<<EOF\n')
handle.write(delta_text)
if not delta_text.endswith('\n'):
handle.write('\n')
handle.write('EOF\n')
if coverage_section:
handle.write('coverage_section<<EOF\n')
handle.write(coverage_section)
if not coverage_section.endswith('\n'):
handle.write('\n')
handle.write('EOF\n')
PY

- name: Build summary body
id: render
run: |
python tools/post_ci_summary.py
env:
RUNS_JSON: ${{ steps.discover.outputs.runs }}
HEAD_SHA: ${{ steps.discover.outputs.head_sha }}
COVERAGE_STATS: ${{ steps.coverage_payloads.outputs.stats_json }}
COVERAGE_DELTA: ${{ steps.coverage_payloads.outputs.delta_json }}
COVERAGE_SECTION: ${{ steps.coverage_payloads.outputs.coverage_section }}

- name: Publish summary
if: ${{ steps.render.outputs.body != '' }}
run: |
python - <<'PY'
import os

body = os.environ.get('SUMMARY_BODY')
if not body:
raise SystemExit(0)

summary_path = os.environ.get('GITHUB_STEP_SUMMARY')
if not summary_path:
raise SystemExit(0)

with open(summary_path, 'a', encoding='utf-8') as handle:
handle.write(body)
if not body.endswith('\n'):
handle.write('\n')
PY
env:
SUMMARY_BODY: ${{ steps.render.outputs.body }}

- name: Persist summary preview
if: ${{ steps.render.outputs.body != '' }}
run: |
python - <<'PY'
import os
from pathlib import Path

body = os.environ.get('SUMMARY_BODY', '')
if not body:
raise SystemExit(0)

preview_path = Path(os.environ.get('PREVIEW_PATH', 'summary_artifacts/post-ci-summary.md'))
preview_path.parent.mkdir(parents=True, exist_ok=True)
preview_path.write_text(body, encoding='utf-8')
PY
env:
SUMMARY_BODY: ${{ steps.render.outputs.body }}
PREVIEW_PATH: ${{ env.ARTIFACT_ROOT }}/post-ci-summary.md

- name: Upload summary preview artifact
if: ${{ steps.render.outputs.body != '' }}
uses: actions/upload-artifact@v4
with:
name: maint-46-post-ci-summary.md
path: ${{ env.ARTIFACT_ROOT }}/post-ci-summary.md
if-no-files-found: warn
retention-days: 7
overwrite: true

- name: Propagate Gate commit status
if: ${{ steps.discover.outputs.head_sha != '' }}
uses: actions/github-script@v7
env:
HEAD_SHA: ${{ steps.discover.outputs.head_sha }}
RUN_CONCLUSION: ${{ github.event.workflow_run.conclusion || '' }}
RUN_STATUS: ${{ github.event.workflow_run.status || '' }}
GATE_RUN_URL: ${{ github.event.workflow_run.html_url || '' }}
with:
script: |
const { propagateGateCommitStatus } = require('./.github/scripts/maint-post-ci.js');
await propagateGateCommitStatus({ github, context, core });
1 change: 1 addition & 0 deletions agents/codex-3053.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!-- bootstrap for codex on issue #3053 -->
1 change: 1 addition & 0 deletions docs/ci/WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ The gate uses the shared `.github/scripts/detect-changes.js` helper to decide wh

* Gate’s `summary` job now emits the consolidated PR comment, uploads `gate-summary.md`, and publishes `gate-coverage.json` / `gate-coverage-delta.json` for downstream consumers.
* [`maint-coverage-guard.yml`](../../.github/workflows/maint-coverage-guard.yml) periodically verifies that the latest Gate run meets baseline coverage expectations.
* [`maint-46-post-ci.yml`](../../.github/workflows/maint-46-post-ci.yml) wakes up after Gate completes, validates the workflow syntax with `actionlint`, downloads the Gate artifacts, renders the consolidated CI summary (including coverage deltas), and republishes the Gate commit status while saving a markdown preview for evidence capture.

## Autofix & Maintenance

Expand Down
16 changes: 12 additions & 4 deletions docs/ci/WORKFLOW_SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ and where to watch the result:
logs show up under the same pull request for easy comparison with Gate.
3. **Merge lands on the default branch.** The Gate summary job aggregates
artifacts from the successful run and applies any low-risk cleanup.
Scheduled maintenance jobs (Maint 45 and Health
Scheduled maintenance jobs (Maint 46 Post CI, Maint 45, and Health
40–44) continue to run on their cadence even when no one is watching,
keeping the repo healthy.
4. **Issue and agents automation picks up queued work.** Labelled issues flow
Expand Down Expand Up @@ -352,14 +352,16 @@ fires where” without diving into the full tables:
Autofix: handled by Gate summary job after Gate completes.
- **Maintenance & repo health**
- **Primary workflows.** Gate summary job inside `pr-00-gate.yml`,
`maint-coverage-guard.yml`, `maint-45-cosmetic-repair.yml`,
and the health guardrails (`health-40` through `health-44`).
`maint-46-post-ci.yml`, `maint-coverage-guard.yml`,
`maint-45-cosmetic-repair.yml`, and the health guardrails
(`health-40` through `health-44`).
- **Triggers.** Combination of the Gate summary job running after Gate,
recurring schedules (health guardrails), and manual dispatch for
Maint 45.
- **Purpose.** Keep the default branch stable after merges, surface drift,
and enforce branch-protection expectations without waiting for the next PR.
- **Where to inspect logs.** Gate summary job: [Gate workflow history](https://github.com/stranske/Trend_Model_Project/actions/workflows/pr-00-gate.yml).
Maint 46: [workflow history](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-46-post-ci.yml).
Maint 45: [workflow history](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-45-cosmetic-repair.yml).
Health guardrails: the [Health 40–44 dashboards](https://github.com/stranske/Trend_Model_Project/actions?query=workflow%3AHealth+40+repo+OR+workflow%3AHealth+41+repo+OR+workflow%3AHealth+42+Actionlint+OR+workflow%3AHealth+43+CI+Signature+Guard+OR+workflow%3AHealth+44+Gate+Branch+Protection).
- **Issue / agents automation**
Expand Down Expand Up @@ -456,7 +458,7 @@ status updates:
| Bucket | Where it runs | YAML entry points | Why it exists |
| --- | --- | --- | --- |
| PR checks | Every pull request event (including `pull_request_target` for fork visibility) | `pr-00-gate.yml` | Keep the default branch green by running the gating matrix before reviewers waste time. |
| Maintenance & repo health | Daily/weekly schedules plus manual dispatch | Gate summary job in `pr-00-gate.yml`, `maint-45-cosmetic-repair.yml`, `health-4x-*.yml` | Scrub lingering CI debt, enforce branch protection, and surface drift before it breaks contributor workflows. |
| Maintenance & repo health | Daily/weekly schedules plus manual dispatch | Gate summary job in `pr-00-gate.yml`, `maint-46-post-ci.yml`, `maint-45-cosmetic-repair.yml`, `health-4x-*.yml` | Scrub lingering CI debt, enforce branch protection, and surface drift before it breaks contributor workflows. |
| Issue / agents automation | Orchestrator dispatch (`workflow_dispatch`, `workflow_call`, `issues`), belt conveyor (`repository_dispatch`, `workflow_run`) | `agents-70-orchestrator.yml`, `agents-71-codex-belt-dispatcher.yml`, `agents-72-codex-belt-worker.yml`, `agents-73-codex-belt-conveyor.yml`, `agents-74-pr-body-writer.yml`, `agents-63-*.yml`, `agents-64-pr-comment-commands.yml`, `agents-64-verify-agent-assignment.yml`, `agents-guard.yml` | Translate labelled issues into automated work while keeping the protected agents surface locked behind guardrails. |
| Error checking, linting, and testing topology | Reusable fan-out invoked by Gate, Gate summary job, and manual triggers | `reusable-10-ci-python.yml`, `reusable-12-ci-docker.yml`, `reusable-16-agents.yml`, `reusable-18-autofix.yml`, `selftest-reusable-ci.yml` | Provide a single source of truth for lint/type/test/container jobs so every caller runs the same matrix with consistent tooling. |

Expand Down Expand Up @@ -489,6 +491,11 @@ Keep this table handy when you are triaging automation: it confirms which workfl
downloads the latest Gate coverage payload plus the trend artifact and
compares them against `config/coverage-baseline.json`, surfacing notices when
coverage dips outside the allowed guard band.
- **Maint 46 Post CI** – `.github/workflows/maint-46-post-ci.yml` listens for
completed Gate runs, runs `actionlint` as a fast syntax guard, downloads the
Gate artifacts, renders the consolidated CI summary (including coverage
deltas) via `tools/post_ci_summary.py`, saves a markdown preview, and
refreshes the Gate commit status so the Checks tab reflects the latest run.
- **Maint 47 Disable Legacy Workflows** – `.github/workflows/maint-47-disable-legacy-workflows.yml`
runs on-demand and disables archived workflows still listed as active in the
Actions UI.
Expand Down Expand Up @@ -593,6 +600,7 @@ Keep this table handy when you are triaging automation: it confirms which workfl
| **Maint 47 Disable Legacy Workflows** (`maint-47-disable-legacy-workflows.yml`, maintenance bucket) | `workflow_dispatch` | Run `tools/disable_legacy_workflows.py` to disable archived workflows that still appear in Actions. | ⚪ Manual | [Maint 47 dispatch](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-47-disable-legacy-workflows.yml) |
| **Maint 50 Tool Version Check** (`maint-50-tool-version-check.yml`, maintenance bucket) | `schedule` (Mondays 8:00 AM UTC), `workflow_dispatch` | Check PyPI for new versions of CI/autofix tools and create/update an issue when updates are available. | ⚪ Scheduled | [Maint 50 version checks](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-50-tool-version-check.yml) |
| **Maint Coverage Guard** (`maint-coverage-guard.yml`, maintenance bucket) | `schedule` (`45 6 * * *`), `workflow_dispatch` | Audit the latest Gate coverage trend artifact and compare it against the baseline, failing when coverage regresses beyond the guard thresholds. | ⚪ Scheduled | [Maint Coverage Guard runs](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-coverage-guard.yml) |
| **Maint 46 Post CI** (`maint-46-post-ci.yml`, maintenance bucket) | `workflow_run` (Gate, `completed`) | Collect the latest Gate run metadata, validate syntax via `actionlint`, render the consolidated CI summary with coverage deltas, publish a markdown preview, and refresh the Gate commit status. | ⚪ Automatic follow-up | [Maint 46 runs](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-46-post-ci.yml) |
| **Maint 45 Cosmetic Repair** (`maint-45-cosmetic-repair.yml`, maintenance bucket) | `workflow_dispatch` | Run pytest + fixers manually and open a labelled PR when changes are required. | ⚪ Manual | [Maint 45 manual entry](https://github.com/stranske/Trend_Model_Project/actions/workflows/maint-45-cosmetic-repair.yml) |
| **Health 40 Repo Selfcheck** (`health-40-repo-selfcheck.yml`, maintenance bucket) | `schedule` (daily) | Capture repository pulse metrics. | ⚪ Scheduled | [Health 40 summary](https://github.com/stranske/Trend_Model_Project/actions/workflows/health-40-repo-selfcheck.yml) |
| **Health 41 Repo Health** (`health-41-repo-health.yml`, maintenance bucket) | `schedule` (weekly) | Perform weekly dependency and repo hygiene sweep. | ⚪ Scheduled | [Health 41 dashboard](https://github.com/stranske/Trend_Model_Project/actions/workflows/health-41-repo-health.yml) |
Expand Down
Loading
Loading