prompt: ship profile validation and smoke environments → complete #1335
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
| name: Lifecycle Drift | |
| # Guards the prompt-file lifecycle (issue #71): the draft/ -> active/ -> | |
| # complete/YYYY/MM invariant and the generated complete/index.md. Fails on: | |
| # - lifecycle check drift (a slug in both active.md and complete.md; a | |
| # complete/ record with no complete.md entry; a file in two states); | |
| # - on pull requests, a stale complete/index.md (regenerate with | |
| # `lifecycle.py index --apply`). | |
| # | |
| # On pushes to main a stale index is SELF-HEALED instead (issue #116): the run | |
| # regenerates it and pushes a bot commit, and fails only if the repair itself | |
| # fails. Mind pushes land directly on main from many concurrent agent | |
| # sessions, so ad-hoc mutations of complete/ (e.g. a manual `git mv`) will | |
| # keep happening — an alarm-only check emailed the human once per push until | |
| # the next `record --apply` happened to regenerate the index. | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "active/**" | |
| - "complete/**" | |
| - "complete.md" | |
| - "active.md" | |
| - "scripts/lifecycle.py" | |
| pull_request: | |
| paths: | |
| - "active/**" | |
| - "complete/**" | |
| - "complete.md" | |
| - "active.md" | |
| - "scripts/lifecycle.py" | |
| workflow_dispatch: | |
| # contents: write is needed by the self-heal push on main; PR runs never push. | |
| permissions: | |
| contents: write | |
| jobs: | |
| drift: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: lifecycle check | |
| run: python3 scripts/lifecycle.py check | |
| - name: index freshness (self-healing on push to main) | |
| run: | | |
| if python3 scripts/lifecycle.py index --check; then | |
| exit 0 | |
| fi | |
| if [ "${GITHUB_EVENT_NAME}" != "push" ]; then | |
| echo "::error::complete/index.md is stale — run 'python3 scripts/lifecycle.py index --apply' on this branch and commit the result" | |
| exit 1 | |
| fi | |
| echo "complete/index.md is stale on main — self-healing" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| # Each attempt rebuilds on the current tip of main, so a concurrent | |
| # push (the usual cause of a rejected push) never needs a rebase or | |
| # conflict resolution — just regenerate and try again. The heal push | |
| # uses the default GITHUB_TOKEN, which does not trigger workflow | |
| # runs, so it cannot loop; convergence is verified before pushing | |
| # regardless. | |
| for attempt in 1 2 3; do | |
| git fetch origin main | |
| git reset --hard FETCH_HEAD | |
| if python3 scripts/lifecycle.py index --check; then | |
| echo "tip of main is already fresh (healed by a concurrent push)" | |
| exit 0 | |
| fi | |
| python3 scripts/lifecycle.py index --apply | |
| if ! python3 scripts/lifecycle.py index --check; then | |
| echo "::error::'index --apply' did not converge — lifecycle.py bug, repair by hand" | |
| exit 1 | |
| fi | |
| git add complete/index.md | |
| git commit -m "lifecycle: self-heal stale complete/index.md" | |
| if git push origin HEAD:main; then | |
| echo "healed on attempt ${attempt}" | |
| exit 0 | |
| fi | |
| echo "push rejected (attempt ${attempt}) — retrying on the new tip of main" | |
| done | |
| echo "::error::could not push the healed index after 3 attempts" | |
| exit 1 |