diff --git a/.github/workflows/master-guardrails.yml b/.github/workflows/master-guardrails.yml index fda934f..2de271e 100644 --- a/.github/workflows/master-guardrails.yml +++ b/.github/workflows/master-guardrails.yml @@ -1,14 +1,14 @@ -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 +# 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: @@ -37,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 @@ -87,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 9c2ebcb..80a8bfb 100644 --- a/BRANCHING.md +++ b/BRANCHING.md @@ -1,56 +1,40 @@ -# 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` | `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` | 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 +> release PRs can be reviewed and merged. --- -## Day-to-day workflow +## 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 -git merge origin/master -# resolve conflicts ONLY in expected files (server.py rare; .env never) -git push -``` +### 1. New work -### 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. --- @@ -58,68 +42,68 @@ feature branch first. 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` +## Required GitHub Branch Protection -(no automation — these are human discipline) +Go to **Settings -> Branches -> Add branch protection rule**: -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 +- **Branch name pattern**: `master` +- 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` +- 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 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. --- -## Required GitHub branch protection (one-time setup in GitHub UI) +## Required GitHub Release Branch Protection -Go to **Settings → Branches → Add branch protection rule**: +To allow PRs into `release` while blocking direct pushes, use a GitHub ruleset +or branch protection rule that requires pull requests. -- **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 - - 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) +Go to **Settings -> Rules -> Rulesets -> New ruleset -> New branch ruleset**: -> 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. +- **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. -> **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. +Also keep `.github/workflows/master-guardrails.yml` required for PRs so release +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.