diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..728bf97 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,14 @@ +# Who has to approve before anything reaches the live branch. +# +# main is where work lands and where Code Quality decides; promote-to-live then moves +# master to whatever passed, and every app reads master directly. So an approval here is +# the last human judgement before a change is being served to wallets in the field. +# +# One approval is enough, but it has to come from one of these two. Requiring two +# approvals would have meant requiring two of two, which stalls whenever one of them is +# the author. +# +# Read-access collaborators cannot approve, so adding a reviewer here also means giving +# them write access. + +* @SatoshiQaziMuhammed @QaziMuhammedKochgiri diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml deleted file mode 100644 index f34b819..0000000 --- a/.github/workflows/auto-merge.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Auto Merge - -on: - workflow_run: - workflows: ["Code Quality"] - types: [completed] - -jobs: - auto-merge: - runs-on: ubuntu-latest - if: > - github.event.workflow_run.conclusion == 'success' && - github.event.workflow_run.event == 'pull_request' - permissions: - contents: write - pull-requests: write - steps: - - name: Find and merge master → main PR - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - HEAD_BRANCH="${{ github.event.workflow_run.head_branch }}" - echo "Workflow ran on branch: $HEAD_BRANCH" - - # Only merge PRs from master to main - if [ "$HEAD_BRANCH" != "master" ]; then - echo "Not a master branch PR, skipping" - exit 0 - fi - - PR_NUMBER=$(gh pr list \ - --repo "$GITHUB_REPOSITORY" \ - --base main \ - --head master \ - --state open \ - --json number \ - --jq '.[0].number') - - if [ -z "$PR_NUMBER" ]; then - echo "No open PR from master to main found, skipping" - exit 0 - fi - - echo "Merging PR #$PR_NUMBER" - gh pr merge "$PR_NUMBER" \ - --repo "$GITHUB_REPOSITORY" \ - --merge \ - --delete-branch=false diff --git a/.github/workflows/auto-pr.yml b/.github/workflows/auto-pr.yml deleted file mode 100644 index 2fce8e7..0000000 --- a/.github/workflows/auto-pr.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Auto PR (master → main) - -on: - push: - branches: [master] - -jobs: - create-pr: - runs-on: ubuntu-latest - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Create or update PR - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - # Check if there's already an open PR from master to main - EXISTING_PR=$(gh pr list --base main --head master --state open --json number --jq '.[0].number') - - if [ -n "$EXISTING_PR" ]; then - echo "PR #$EXISTING_PR already exists — new commits will appear automatically" - exit 0 - fi - - echo "Creating new PR: master → main" - if gh pr create \ - --base main \ - --head master \ - --title "Sync: master → main" \ - --body "Automated PR to sync master branch changes to main. - - This PR was created automatically and will be merged once CI checks pass."; then - echo "PR created successfully" - else - echo "PR creation skipped (branches may already be in sync)" - fi diff --git a/.github/workflows/promote-to-live.yml b/.github/workflows/promote-to-live.yml new file mode 100644 index 0000000..20bfc89 --- /dev/null +++ b/.github/workflows/promote-to-live.yml @@ -0,0 +1,65 @@ +name: Promote main → master + +# master is the live branch: every app reads its config straight from +# raw.githubusercontent.com/.../master/... . Nothing should reach it that has not +# passed Code Quality first. +# +# The flow used to run the other way. auto-pr.yml fired on a push to *master* and +# opened a master → main PR, so content was already live before a single check ran — +# the gate sat downstream of the thing it was meant to gate. A check that reports +# "the live config is broken" is not a gate. +# +# It also made master unwritable by the front door: five checks were required there and +# no workflow triggered on a PR into master, so the only way to change the live branch +# was to bypass its own protection. A rule that can only be bypassed teaches people to +# bypass it. +# +# Now: work lands on main, Code Quality decides, and only then is master moved to match. + +on: + workflow_run: + workflows: ["Code Quality"] + types: [completed] + +permissions: + contents: write + +jobs: + promote: + # Only a green run, only on main, and only for a real push — a pull_request run + # tests a merge commit that does not exist on main yet. + if: > + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.head_branch == 'main' && + github.event.workflow_run.event == 'push' + runs-on: ubuntu-latest + + steps: + - name: Checkout the exact commit that passed + uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + + - name: Refuse to promote a commit that is not on main + run: | + git fetch origin main + if ! git merge-base --is-ancestor "${{ github.event.workflow_run.head_sha }}" origin/main; then + echo "::error::${{ github.event.workflow_run.head_sha }} is not an ancestor of main — refusing" + exit 1 + fi + echo "Commit is on main." + + - name: Promote + run: | + SHA="${{ github.event.workflow_run.head_sha }}" + + # --force-with-lease, not --force: if master has moved since this job read it, + # the push is refused rather than silently discarding whatever moved it. + git push --force-with-lease origin "$SHA":refs/heads/master + + echo "master is now $SHA" + echo "### Promoted to live" >> "$GITHUB_STEP_SUMMARY" + echo "\`master\` → \`$SHA\`" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "Apps read this branch directly; the change is live as soon as their config refreshes." >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/sync-nova-base.yml b/.github/workflows/sync-nova-base.yml index 0fb7677..561a59d 100644 --- a/.github/workflows/sync-nova-base.yml +++ b/.github/workflows/sync-nova-base.yml @@ -100,7 +100,26 @@ jobs: sync dependencies - - name: Auto-merge if tests pass + # Each daily run opens a new sync PR. Nothing closed the previous one, so they + # accumulated: 31 branches spanning 2026-02-10 to 2026-08-08 were cleared out by + # hand on 2026-08-11, every one of them superseded by the next day's run. + # + # A sync is a snapshot of upstream. The newest one contains everything the older + # ones did, so an older open sync PR is never the right thing to merge — it is + # noise that hides whether anything is genuinely waiting for review. + - name: Close superseded sync PRs if: steps.check_output.outputs.output_changed == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + KEEP: sync/nova-base-${{ env.NOVA_COMMIT }} run: | - echo "PR created. Review and merge to apply Nova updates." + gh pr list --repo "$GITHUB_REPOSITORY" --state open --label sync \ + --json number,headRefName --jq '.[] | @base64' | while read -r row; do + decoded=$(echo "$row" | base64 -d) + NUM=$(echo "$decoded" | python3 -c 'import json,sys; print(json.load(sys.stdin)["number"])') + HEAD=$(echo "$decoded" | python3 -c 'import json,sys; print(json.load(sys.stdin)["headRefName"])') + [ "$HEAD" = "$KEEP" ] && continue + echo "Closing #$NUM ($HEAD) — superseded by $KEEP" + gh pr close "$NUM" --repo "$GITHUB_REPOSITORY" --delete-branch \ + --comment "Superseded by \`$KEEP\`. A sync is a snapshot of upstream, so the newer one contains everything this did." + done