delivery-snapshot #68
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
| # Delivery snapshot — regenerate the cockpit's delivery dashboard from real merge | |
| # evidence on every merge to master, and FAIL CLOSED if the rendered snapshot | |
| # would misreport delivery. | |
| # | |
| # Governance intent (registry/board-spec.yaml): GitHub Projects is a render | |
| # target, not the truth. The truth is merged PRs and issue events. This job | |
| # recomputes from that evidence so the dashboard cannot drift from what shipped. | |
| name: delivery-snapshot | |
| on: | |
| push: | |
| branches: [master] | |
| paths: | |
| - 'socioprophet-web/client-vue/**' | |
| - '.github/workflows/delivery-snapshot.yml' | |
| schedule: | |
| # Refresh daily so the board reflects reality even in a quiet week. | |
| - cron: '17 6 * * *' | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: read | |
| concurrency: | |
| group: delivery-snapshot | |
| cancel-in-progress: true | |
| jobs: | |
| regenerate: | |
| runs-on: ubuntu-latest | |
| defaults: | |
| run: | |
| working-directory: socioprophet-web/client-vue | |
| steps: | |
| # Prefer a minted, short-lived GitHub App installation token so the snapshot | |
| # PR is AUTHORED BY THE APP. GitHub deliberately does not start workflow runs | |
| # for events triggered by the default GITHUB_TOKEN (recursion guard), so a PR | |
| # opened with github.token never triggers the required `gate / check` and can | |
| # therefore never auto-merge. An App-authored PR triggers the gate normally. | |
| # No long-lived PAT is stored — the token is minted at runtime and expires. | |
| # | |
| # Until the App is configured this falls back to github.token: the PR still | |
| # opens and is fully gated (never a direct push, never fail-open), it just | |
| # needs its gate triggered once (e.g. a maintainer close/reopen) to merge. | |
| # One-time setup: create a GitHub App with contents:write + pull-requests:write | |
| # on SocioProphet/socioprophet, install it, then set repo var | |
| # DELIVERY_SNAPSHOT_APP_ID and repo secret DELIVERY_SNAPSHOT_APP_KEY. | |
| - name: Mint scoped GitHub App token (optional) | |
| id: apptoken | |
| if: ${{ vars.DELIVERY_SNAPSHOT_APP_ID != '' }} | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ vars.DELIVERY_SNAPSHOT_APP_ID }} | |
| private-key: ${{ secrets.DELIVERY_SNAPSHOT_APP_KEY }} | |
| owner: SocioProphet | |
| repositories: socioprophet | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| # Persist whichever token authors the snapshot PR, so the push + PR are | |
| # attributed to the App when configured (falls back to github.token). | |
| token: ${{ steps.apptoken.outputs.token || github.token }} | |
| # board-spec.yaml is NOT in this repo — its canonical home is | |
| # SocioProphet/sociosphere (a public repo). Consume the single source of | |
| # truth via a sparse cross-repo checkout rather than forking a copy in here; | |
| # pointing --board-spec at a nonexistent path left "Board spec resolved" | |
| # permanently unverified. Uses github.token: sociosphere is public, and the | |
| # App token is scoped to socioprophet only. | |
| - name: Fetch canonical board spec (sociosphere is the source of truth) | |
| uses: actions/checkout@v4 | |
| with: | |
| repository: SocioProphet/sociosphere | |
| sparse-checkout: registry/board-spec.yaml | |
| sparse-checkout-cone-mode: false | |
| path: .sociosphere | |
| token: ${{ github.token }} | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Regenerate snapshot from merge evidence | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| node scripts/generate-delivery-snapshot.mjs \ | |
| --orgs "SocioProphet,SourceOS-Linux" \ | |
| --days 14 \ | |
| --history-weeks 10 \ | |
| --board-spec "${GITHUB_WORKSPACE}/.sociosphere/registry/board-spec.yaml" | |
| # The gate. A snapshot that could not collect live evidence, or that | |
| # asserts a value with no evidence ref, must not be published as status. | |
| # Integrity fails the build (the snapshot would misinform). | |
| # Health findings are printed and PUBLISHED — suppressing the dashboard when | |
| # flow degrades is exactly backwards. | |
| - name: Verify snapshot integrity (fail closed) | |
| run: node scripts/verify-delivery-snapshot.mjs | |
| - name: Typecheck and test the surface | |
| run: | | |
| # client-vue commits a pnpm-lock.yaml, not a package-lock.json, so | |
| # `npm ci` (which requires package-lock/npm-shrinkwrap) fails EUSAGE and | |
| # the whole snapshot job dies before it can commit the refreshed data. | |
| # Match the working convention of client-vue-product-build.yml, which | |
| # installs with `npm install`. | |
| npm install --no-audit --no-fund | |
| npx vue-tsc --noEmit | |
| npx vitest run src/__tests__/deliveryDashboard.test.ts src/__tests__/deliveryEconomics.test.ts | |
| # Publish the refreshed snapshot through the SAME gate as everything else. | |
| # master is protected (required check "gate / check"), so a direct push is — | |
| # correctly — rejected (GH006). Pushing anyway, or making that push | |
| # non-fatal, would be fail-open: it silently disables the refresh. Instead | |
| # the snapshot arrives as a gated, auto-merging PR from a bot-owned branch. | |
| - name: Open or update the snapshot PR | |
| env: | |
| # App token when configured (so the PR triggers the gate and auto-merges); | |
| # github.token otherwise (PR opens and is gated, needs its gate triggered). | |
| GH_TOKEN: ${{ steps.apptoken.outputs.token || github.token }} | |
| SNAPSHOT: socioprophet-web/client-vue/src/data/deliverySnapshot.ts | |
| BRANCH: bot/delivery-snapshot | |
| run: | | |
| cd "$GITHUB_WORKSPACE" | |
| if git diff --quiet -- "$SNAPSHOT"; then | |
| echo "snapshot unchanged — nothing to propose" | |
| exit 0 | |
| fi | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Carry exactly the regenerated snapshot onto a dedicated bot branch, | |
| # rebuilt from the current master each run so the PR only ever diffs the | |
| # snapshot file. | |
| git checkout -B "$BRANCH" | |
| git add "$SNAPSHOT" | |
| git commit -m "chore(cockpit): refresh delivery snapshot from merge evidence" | |
| git push --force origin "HEAD:$BRANCH" | |
| # Open the PR once; later runs update it via the force-push above. | |
| if gh pr view "$BRANCH" --json state --jq '.state' 2>/dev/null | grep -qx OPEN; then | |
| echo "existing snapshot PR updated" | |
| else | |
| gh pr create \ | |
| --base master \ | |
| --head "$BRANCH" \ | |
| --title "chore(cockpit): refresh delivery snapshot from merge evidence" \ | |
| --body $'Automated refresh of the cockpit delivery snapshot from real merge evidence.\n\nThe workflow never pushes to master directly — branch protection correctly rejects that. This PR flows through the same `gate / check` as every other change, and auto-merge is enabled so it merges the moment the gate passes.' | |
| fi | |
| # Let it merge itself once the required gate passes — no standing write | |
| # access to master, no fail-open. Enabling auto-merge is best-effort: | |
| # the load-bearing outcome (an open, gated PR) is already achieved. | |
| gh pr merge "$BRANCH" --squash --auto \ | |
| || echo "auto-merge not set; PR remains open and gated for review/merge" |