From e1fd84127b52d9df90e7f8d13073bd20aca33ffe Mon Sep 17 00:00:00 2001 From: Jaydeep Rusia Date: Fri, 8 May 2026 17:34:41 +0530 Subject: [PATCH 1/2] Add lock to prevent direct commits to release branches --- .github/workflows/master-guardrails.yml | 9 +++--- BRANCHING.md | 40 ++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/.github/workflows/master-guardrails.yml b/.github/workflows/master-guardrails.yml index fda934f..993cd11 100644 --- a/.github/workflows/master-guardrails.yml +++ b/.github/workflows/master-guardrails.yml @@ -1,14 +1,15 @@ -name: Master branch guardrails +name: Protected branch guardrails -# Runs on every PR that targets master. Blocks merges that would re-introduce -# Bedrock/JWT defaults, leak credentials, or remove the provider abstraction. +# Runs on every PR that targets master/main/release. Blocks merges that would +# re-introduce Bedrock/JWT defaults, leak credentials, or remove the provider +# abstraction. # # What it does NOT do: enforce required reviews / required checks. Those toggles # live in GitHub Settings > Branches > Branch protection rules. See BRANCHING.md. on: pull_request: - branches: [master, main] + branches: [master, main, release] jobs: forbidden-content-scan: diff --git a/BRANCHING.md b/BRANCHING.md index 9c2ebcb..707c9ed 100644 --- a/BRANCHING.md +++ b/BRANCHING.md @@ -4,24 +4,29 @@ This repo runs in **two deployment modes** from a single codebase. The provider abstraction in `backend/llm_provider.py` and `backend/auth_provider.py` lets the SAME source code switch between modes via env vars only. -| Branch | LLM_PROVIDER | AUTH_PROVIDER | Where it runs | -| ------------- | ------------ | ------------- | ---------------------------- | -| `master` | `emergent` | `emergent` | emergent.sh preview + deploy | -| `local_setup` | `bedrock` | `jwt` | your local / self-hosted | +| Branch | LLM_PROVIDER | AUTH_PROVIDER | Where it runs | +| --------- | ------------ | ------------- | ---------------------------- | +| `master` | `bedrock` | `jwt` | emergent.sh preview + deploy | +| `release` | n/a | n/a | PR-only release branch | > **Source of truth: `master`.** Features land here first. +> **`release` is PR-only.** Nobody should push directly or force-update it, but +> release PRs can be reviewed and merged. --- ## Day-to-day workflow ### 1. New feature on Emergent.sh + ``` work in emergent.sh chat → "Save to GitHub" → master ``` + Emergent pushes to `master`. Done. ### 2. Sync local with the latest master + ``` git checkout local_setup git fetch origin @@ -31,15 +36,18 @@ git push ``` ### 3. Local-only changes (deployment configs, infra) + ``` git checkout local_setup # make changes git commit -m "infra: bump fly.toml memory" git push ``` + **Never** PR these back to master. ### 4. Polish/refactor done locally that SHOULD reach master + ``` git checkout master git pull @@ -49,6 +57,7 @@ git cherry-pick git push -u origin feat/my-polish # open PR feat/my-polish → master ``` + **Do not** PR `local_setup → master` directly. Always cherry-pick into a feature branch first. @@ -105,6 +114,29 @@ Go to **Settings → Branches → Add branch protection rule**: > `@your-org/maintainers`). Without that, the `Require review from Code Owners` > rule has no one to assign. +## Required GitHub release branch protection + +To allow PRs into `release` while blocking direct pushes, use a GitHub ruleset +or branch protection rule that requires pull requests. + +Go to **Settings -> Rules -> Rulesets -> New ruleset -> New branch ruleset**: + +- **Ruleset name**: `Protect release branch` +- **Enforcement status**: `Active` +- **Target branches**: include by pattern, `release` +- Enable: + - **Require a pull request before merging** + - **Require status checks to pass** + - **Restrict deletions** + - **Block force pushes** + - **Require linear history** if available +- Do not enable **Restrict updates** if you want normal PR merges to work. +- If you are solo, keep required approvals at `0` or add yourself as an allowed + bypass actor only if GitHub requires an escape hatch. + +Also keep `.github/workflows/master-guardrails.yml` required for PRs so release +PRs get the same secret/provider checks as `master`. + --- ## Troubleshooting From fd570ff55a817b3de3fe3c7c3985277f6e59da5b Mon Sep 17 00:00:00 2001 From: Jaydeep Rusia Date: Fri, 8 May 2026 17:39:39 +0530 Subject: [PATCH 2/2] Update branch guardrails --- .github/workflows/master-guardrails.yml | 52 ++------- BRANCHING.md | 138 ++++++++---------------- 2 files changed, 51 insertions(+), 139 deletions(-) diff --git a/.github/workflows/master-guardrails.yml b/.github/workflows/master-guardrails.yml index 993cd11..2de271e 100644 --- a/.github/workflows/master-guardrails.yml +++ b/.github/workflows/master-guardrails.yml @@ -1,8 +1,7 @@ name: Protected branch guardrails # Runs on every PR that targets master/main/release. Blocks merges that would -# re-introduce Bedrock/JWT defaults, leak credentials, or remove the provider -# abstraction. +# leak credentials or remove the provider abstraction. # # What it does NOT do: enforce required reviews / required checks. Those toggles # live in GitHub Settings > Branches > Branch protection rules. See BRANCHING.md. @@ -38,38 +37,16 @@ jobs: exit 1 fi - - name: 2. Default providers must remain "emergent" in backend/.env.example - run: | - if [ -f backend/.env.example ]; then - if ! grep -qE '^LLM_PROVIDER=emergent\b' backend/.env.example; then - echo "::error::backend/.env.example must default LLM_PROVIDER=emergent on master." - exit 1 - fi - if ! grep -qE '^AUTH_PROVIDER=emergent\b' backend/.env.example; then - echo "::error::backend/.env.example must default AUTH_PROVIDER=emergent on master." - exit 1 - fi - fi - - - name: 3. Provider abstraction files must exist + - name: 2. Provider abstraction files must exist run: | for f in backend/llm_provider.py backend/auth_provider.py; do if [ ! -f "$f" ]; then - echo "::error::$f is missing. The provider abstraction must remain on master." + echo "::error::$f is missing. The provider abstraction must remain." exit 1 fi done - - name: 4. emergentintegrations must remain a dependency - run: | - if [ -f backend/requirements.txt ]; then - if ! grep -qi '^emergentintegrations' backend/requirements.txt; then - echo "::error::backend/requirements.txt no longer pins emergentintegrations. master must keep it." - exit 1 - fi - fi - - - name: 5. No hardcoded AWS / JWT secrets + - name: 3. No hardcoded AWS / JWT secrets run: | # Scan only the diff (not the whole repo) so existing acceptable strings don't trip it if grep -nE 'AKIA[0-9A-Z]{16}' /tmp/patch.diff; then @@ -88,27 +65,10 @@ jobs: exit 1 fi - - name: 6. backend/.env (the live one, not example) must default to emergent if committed - # If .env somehow ends up in the diff (e.g., gitignore was relaxed), enforce defaults. + - name: 4. backend/.env must never be committed + # If .env somehow ends up in the diff (e.g., gitignore was relaxed), block it. run: | if [ -f backend/.env ] && grep -qE '^backend/\.env$' /tmp/changed.txt; then echo "::error::backend/.env should never be committed. .gitignore is the line of defense." exit 1 fi - - branch-name-check: - name: Branch-name & PR-title check for local_setup cherry-picks - runs-on: ubuntu-latest - steps: - - name: Verify PRs from local_setup are explicitly labelled - env: - HEAD: ${{ github.head_ref }} - TITLE: ${{ github.event.pull_request.title }} - run: | - if [ "$HEAD" = "local_setup" ] || [ "$HEAD" = "local-setup" ]; then - if ! echo "$TITLE" | grep -qE '\[from local_setup\]|\[ALLOW-LOCAL-SETUP\]'; then - echo "::error::This PR is from $HEAD. To prevent accidental merges of self-hosted defaults, prefix the PR title with [from local_setup] (and re-read the Provider hygiene checklist)." - exit 1 - fi - echo "PR is explicitly labelled; proceeding. Reviewer must still complete the Provider hygiene checklist." - fi diff --git a/BRANCHING.md b/BRANCHING.md index 707c9ed..80a8bfb 100644 --- a/BRANCHING.md +++ b/BRANCHING.md @@ -1,13 +1,13 @@ -# Branching & merge policy +# Branching & Merge Policy -This repo runs in **two deployment modes** from a single codebase. The provider -abstraction in `backend/llm_provider.py` and `backend/auth_provider.py` lets -the SAME source code switch between modes via env vars only. +This repo keeps provider logic behind `backend/llm_provider.py` and +`backend/auth_provider.py` so auth and LLM behavior can be configured without +rewriting product code. -| Branch | LLM_PROVIDER | AUTH_PROVIDER | Where it runs | -| --------- | ------------ | ------------- | ---------------------------- | -| `master` | `bedrock` | `jwt` | emergent.sh preview + deploy | -| `release` | n/a | n/a | PR-only release branch | +| Branch | LLM_PROVIDER | AUTH_PROVIDER | Where it runs | +| --------- | ------------ | ------------- | -------------------------- | +| `master` | `bedrock` | `jwt` | primary development branch | +| `release` | n/a | n/a | PR-only release branch | > **Source of truth: `master`.** Features land here first. > **`release` is PR-only.** Nobody should push directly or force-update it, but @@ -15,106 +15,61 @@ the SAME source code switch between modes via env vars only. --- -## Day-to-day workflow +## Day-to-day Workflow -### 1. New feature on Emergent.sh +### 1. New work ``` -work in emergent.sh chat → "Save to GitHub" → master -``` - -Emergent pushes to `master`. Done. - -### 2. Sync local with the latest master - -``` -git checkout local_setup -git fetch origin -git merge origin/master -# resolve conflicts ONLY in expected files (server.py rare; .env never) -git push -``` - -### 3. Local-only changes (deployment configs, infra) - -``` -git checkout local_setup +git checkout master +git pull +git checkout -b feat/my-change # make changes -git commit -m "infra: bump fly.toml memory" -git push +git push -u origin feat/my-change +# open PR feat/my-change -> master ``` -**Never** PR these back to master. - -### 4. Polish/refactor done locally that SHOULD reach master +### 2. Release work ``` -git checkout master -git pull -git checkout -b feat/my-polish -git cherry-pick -# verify nothing provider-specific snuck in (see checklist below) -git push -u origin feat/my-polish -# open PR feat/my-polish → master +git checkout -b release/v0.1.0 master +git push -u origin release/v0.1.0 +# open PR release/v0.1.0 -> release ``` -**Do not** PR `local_setup → master` directly. Always cherry-pick into a -feature branch first. - --- ## What MUST stay on `master` These are enforced automatically by `.github/workflows/master-guardrails.yml`: -1. `backend/.env.example` defaults to `LLM_PROVIDER=emergent` and `AUTH_PROVIDER=emergent` -2. `backend/llm_provider.py` and `backend/auth_provider.py` exist -3. `emergentintegrations` stays in `backend/requirements.txt` -4. No `.env` file is committed -5. No real AWS keys / JWT secrets in the diff +1. `backend/llm_provider.py` and `backend/auth_provider.py` exist +2. No `.env` file is committed +3. No real AWS keys / JWT secrets in the diff The PR template's checklist asks reviewers to verify the same. --- -## What MUST stay on `local_setup` - -(no automation — these are human discipline) - -1. `backend/.env`: `LLM_PROVIDER=bedrock`, `AUTH_PROVIDER=jwt`, plus AWS + JWT secrets -2. Any deployment configs your stack needs (Dockerfile tweaks, `fly.toml`, - `nginx.conf`, k8s manifests, etc.) — keep them in a `deploy/` folder so they - are easy to keep separate during cherry-picks -3. Optional `requirements.local.txt` if you ever need self-hosted-only Python - deps that should NOT ship to master - ---- - -## Required GitHub branch protection (one-time setup in GitHub UI) +## Required GitHub Branch Protection -Go to **Settings → Branches → Add branch protection rule**: +Go to **Settings -> Branches -> Add branch protection rule**: - **Branch name pattern**: `master` -- ✅ Require a pull request before merging - - ✅ Require approvals: at least 1 - - ✅ **Require review from Code Owners** ← enables `.github/CODEOWNERS` -- ✅ Require status checks to pass before merging +- Require a pull request before merging +- Require approvals if you want review gates +- Require review from Code Owners if `.github/CODEOWNERS` is configured +- Require status checks to pass before merging - Add: `Scan for forbidden content` - - Add: `Branch-name & PR-title check for local_setup cherry-picks` -- ✅ Require branches to be up to date before merging -- ✅ Do not allow bypassing the above settings -- ✅ Restrict pushes that create matching branches (optional — locks down direct pushes) +- Require branches to be up to date before merging +- Do not allow bypassing the above settings +- Restrict pushes that create matching branches if you want to block direct pushes -> The `master-guardrails` workflow MUST run at least once on a PR before its -> jobs appear in the "Status checks" picker. Open a no-op PR (e.g., editing -> this file) to surface them. +> The workflow must run at least once on a PR before its jobs appear in the +> "Status checks" picker. Open a small PR to surface them. -> **Before any of this kicks in, edit `.github/CODEOWNERS`** and replace -> `@YOUR-GITHUB-USERNAME` with your real GitHub handle (or a team handle like -> `@your-org/maintainers`). Without that, the `Require review from Code Owners` -> rule has no one to assign. +--- -## Required GitHub release branch protection +## Required GitHub Release Branch Protection To allow PRs into `release` while blocking direct pushes, use a GitHub ruleset or branch protection rule that requires pull requests. @@ -142,16 +97,13 @@ PRs get the same secret/provider checks as `master`. ## Troubleshooting **Q: `git merge origin/master` produced a conflict in `backend/.env`.** -A: That should never happen — `.env` is gitignored. Double-check your local -checkout doesn't have `backend/.env` tracked (`git rm --cached backend/.env`). - -**Q: I accidentally pushed Bedrock defaults to master.** -A: The `master-guardrails` action will block the PR. If somehow it merged, -revert with `git revert ` and force-restore the `emergent` -defaults in `backend/.env.example`. - -**Q: I want to test JWT mode against the live preview without breaking -master.** -A: Don't. Test it on `local_setup` or in a temporary branch. The Emergent -preview env always boots from master's `.env`, which must stay on emergent -defaults. +A: That should never happen because `.env` is gitignored. Double-check your local +checkout does not have `backend/.env` tracked: + +``` +git rm --cached backend/.env +``` + +**Q: The guardrail workflow blocked a PR.** +A: Fix the flagged file in your feature branch, push again, and let the PR checks +rerun.