From 88cad5b57231b697dffd2add226f44aab10b11ef Mon Sep 17 00:00:00 2001 From: dso6060 Date: Mon, 22 Jun 2026 19:11:38 +0530 Subject: [PATCH] Add friedso_v1 production deploy branch workflow. Document that friedso.com serves friedso_v1 (not main), add deploy_friedso_production.sh with branch guard and validate/build/bundle pipeline, and extend CI to validate friedso_v1 pushes. Co-authored-by: Cursor --- .github/GOVERNANCE.md | 15 ++ .github/PUBLISH_CHECKLIST.md | 1 + .github/workflows/validate.yml | 2 +- README.md | 2 +- jem/docs/SESSION_WORKFLOW.md | 10 +- jem/docs/V1_RELEASE_RUNBOOK.md | 28 +++- jem/scripts/deploy_friedso_production.sh | 178 +++++++++++++++++++++++ jem/scripts/deploy_prep.sh | 2 + 8 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 jem/scripts/deploy_friedso_production.sh diff --git a/.github/GOVERNANCE.md b/.github/GOVERNANCE.md index dce347d..191c8a6 100644 --- a/.github/GOVERNANCE.md +++ b/.github/GOVERNANCE.md @@ -54,3 +54,18 @@ Widening scope (new states, gap-registry entities) requires an explicit maintain ## Deploy **Canonical demo (attribution):** https://friedso.com/apps/jem/ — production deploy **founder only** unless delegated. Mirrors may host `jem/web/` + `graph.json` with courtesy credit to that URL. + +### Branches + +| Branch | Purpose | +|--------|---------| +| `main` | Active development (data + UI); may move ahead of production | +| `friedso_v1` | **Production line** for friedso.com — deploy only from here | + +**Rules (GitHub ruleset `friedso_v1 production deploy`):** + +- Changes reach `friedso_v1` via **pull request** (no direct pushes). +- Only [@dso6060](https://github.com/dso6060) can **merge** PRs into `friedso_v1` (ruleset bypass on pull requests). +- Co-maintainers work on `main`; founder promotes to `friedso_v1` after `./jem/scripts/deploy_friedso_production.sh` + smoke tests ([`V1_RELEASE_RUNBOOK.md`](../jem/docs/V1_RELEASE_RUNBOOK.md)). + +Personal repos cannot use classic “restrict push to user” branch protection; the ruleset above enforces the same intent. diff --git a/.github/PUBLISH_CHECKLIST.md b/.github/PUBLISH_CHECKLIST.md index 98e3960..ca11d73 100644 --- a/.github/PUBLISH_CHECKLIST.md +++ b/.github/PUBLISH_CHECKLIST.md @@ -22,6 +22,7 @@ Repository: **https://github.com/dso6060/jem_prototype** (public) - [ ] **Settings → General → Features:** enable **Issues** and **Discussions** - [ ] **Discussions → Categories:** ensure `Q&A` and `Disputes` (or map templates to existing categories) - [ ] **Settings → Branches → `main`:** require PR, require status checks (`Validate JEM Data`), require CODEOWNERS review +- [x] **Branch `friedso_v1`:** production deploy line for friedso.com; ruleset `friedso_v1 production deploy` (PR required; merge bypass @dso6060 only) - [x] Invite co-maintainer **@Prajna1999** as collaborator (admin); `CODEOWNERS` updated ## 4. Labels (create if not auto-created) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 06f2475..f38790d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -7,7 +7,7 @@ on: - 'jem/scripts/**' - '.github/workflows/validate.yml' push: - branches: [main] + branches: [main, friedso_v1] paths: - 'jem/data/**' - 'jem/scripts/**' diff --git a/README.md b/README.md index d4cdeb9..591cd45 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ cd .. - **Local preview:** `cd jem/web && python3 -m http.server 8080` (ensure `public/graph.json` resolves). - **Static hosts:** upload `jem/web/` plus `graph.json` as `public/graph.json` (Netlify, S3, GitHub Pages, nginx, etc.). -Production deploy is **maintainer-only** (local helpers not in this repo — see [`jem/scripts/MAINTAINER_SCRIPTS.md`](jem/scripts/MAINTAINER_SCRIPTS.md)). Public workflow: [`jem/docs/SESSION_WORKFLOW.md`](jem/docs/SESSION_WORKFLOW.md) · [`deploy_prep.sh`](jem/scripts/deploy_prep.sh). +Production deploy is **maintainer-only** from branch **`friedso_v1`** (see [`.github/GOVERNANCE.md`](.github/GOVERNANCE.md)). Public workflow: [`jem/docs/SESSION_WORKFLOW.md`](jem/docs/SESSION_WORKFLOW.md) · [`deploy_friedso_production.sh`](jem/scripts/deploy_friedso_production.sh) · [`deploy_prep.sh`](jem/scripts/deploy_prep.sh). GitHub: https://github.com/dso6060/jem_prototype — Actions validates PRs; **does not auto-deploy**. diff --git a/jem/docs/SESSION_WORKFLOW.md b/jem/docs/SESSION_WORKFLOW.md index 79dfc2b..35209d5 100644 --- a/jem/docs/SESSION_WORKFLOW.md +++ b/jem/docs/SESSION_WORKFLOW.md @@ -67,7 +67,15 @@ cp build/graph.staging.json ../graph.json ## Deploy / release (v1.0.0) -See [`V1_RELEASE_RUNBOOK.md`](V1_RELEASE_RUNBOOK.md) — preflight, rsync, smoke tests, git tag. +Production serves branch **`friedso_v1`**, not `main`. See [`V1_RELEASE_RUNBOOK.md`](V1_RELEASE_RUNBOOK.md) — preflight, rsync, smoke tests, git tag. + +```bash +git checkout friedso_v1 && git pull --ff-only origin friedso_v1 +./jem/scripts/deploy_friedso_production.sh +# optional: JEM_REMOTE=... ./jem/scripts/deploy_friedso_production.sh --deploy +``` + +Promote `main` → `friedso_v1` via PR (founder merges after local validate + smoke). --- diff --git a/jem/docs/V1_RELEASE_RUNBOOK.md b/jem/docs/V1_RELEASE_RUNBOOK.md index 7ca305f..066a442 100644 --- a/jem/docs/V1_RELEASE_RUNBOOK.md +++ b/jem/docs/V1_RELEASE_RUNBOOK.md @@ -5,16 +5,37 @@ **Canonical public demo (attribution only):** https://friedso.com/apps/jem/ +**Deploy branch:** `friedso_v1` — production always serves this branch. `main` continues for active development; promote to `friedso_v1` via PR after validate + smoke test (founder merges only — see [`GOVERNANCE.md`](../../.github/GOVERNANCE.md)). + --- ## 0. Preflight (run locally) -**Recommended:** run `./jem/scripts/deploy_prep.sh`, then ship `graph.json` + `jem/web/` per §1 below. +**Recommended:** checkout `friedso_v1`, then run: + +```bash +git fetch origin +git checkout friedso_v1 +git pull --ff-only origin friedso_v1 +./jem/scripts/deploy_friedso_production.sh # validate → derive → build → bundle +./jem/scripts/deploy_friedso_production.sh --deploy # rsync when JEM_REMOTE is set +``` + +Or run `./jem/scripts/deploy_prep.sh` for checks only, then ship `graph.json` + `jem/web/` per §1 below. Maintainers with a **local copy** of `build_friedso_deploy_bundle.sh` (not in public GitHub — see `jem/scripts/MAINTAINER_SCRIPTS.md`) can build `_deploy_bundle/jem-web-*` and optional `--deploy`. Fix any errors before upload/rsync. See [`SESSION_WORKFLOW.md`](SESSION_WORKFLOW.md) for the daily pipeline and **graph overwrite** risks. +### Promote `main` → `friedso_v1` + +When `main` has changes you want on friedso.com: + +```bash +gh pr create --base friedso_v1 --head main --title "deploy: promote main to friedso_v1" +# Founder: run deploy_friedso_production.sh locally on friedso_v1 after merge, then smoke §2 +``` + --- ## 1. Deploy to production (static host) @@ -86,12 +107,13 @@ If `graph.json` 404s or shows ~13 entities, the deploy did not ship the repo-roo ```bash cd /path/to/jem/repo -git status # clean, on main (or release branch) +git checkout friedso_v1 +git status # clean, on friedso_v1 git log -1 --oneline git tag -a v1.0.0 -m "JEM v1.0.0 — 1,103 entities, full state/UT packs, UI refresh" -# git push origin main +# git push origin friedso_v1 # git push origin v1.0.0 ``` diff --git a/jem/scripts/deploy_friedso_production.sh b/jem/scripts/deploy_friedso_production.sh new file mode 100644 index 0000000..76f7dd0 --- /dev/null +++ b/jem/scripts/deploy_friedso_production.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# Production deploy for https://friedso.com/apps/jem/ +# +# Always run from the friedso_v1 branch (the deploy line). main may move ahead; +# friedso_v1 is what production serves after validate + smoke test. +# +# Usage (from repo root): +# ./jem/scripts/deploy_friedso_production.sh +# ./jem/scripts/deploy_friedso_production.sh --deploy +# JEM_REMOTE='user@host:~/path/to/apps/jem' ./jem/scripts/deploy_friedso_production.sh --deploy +# +# Promote main → friedso_v1 (founder merges PR after local smoke on a branch): +# git fetch origin +# gh pr create --base friedso_v1 --head main --title "deploy: promote main to friedso_v1" +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +JEM_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +REPO_ROOT="$(cd "${JEM_ROOT}/.." && pwd)" +GRAPH="${REPO_ROOT}/graph.json" +DEPLOY_BRANCH="${JEM_DEPLOY_BRANCH:-friedso_v1}" +MIN_ENTITIES=400 +DO_DEPLOY=0 +BUNDLE_NAME="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --deploy) DO_DEPLOY=1; shift ;; + --branch) + DEPLOY_BRANCH="$2" + shift 2 + ;; + --name) + BUNDLE_NAME="$2" + shift 2 + ;; + -h|--help) + sed -n '2,18p' "$0" + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + exit 1 + ;; + esac +done + +cd "${REPO_ROOT}" + +echo "==> Ensure deploy branch (${DEPLOY_BRANCH})" +git fetch origin "${DEPLOY_BRANCH}" 2>/dev/null || git fetch origin + +CURRENT="$(git rev-parse --abbrev-ref HEAD)" +if [[ "${CURRENT}" != "${DEPLOY_BRANCH}" ]]; then + if ! git diff --quiet || ! git diff --cached --quiet; then + echo "ERROR: working tree dirty; commit or stash before switching to ${DEPLOY_BRANCH}" >&2 + exit 1 + fi + git checkout "${DEPLOY_BRANCH}" +fi + +LOCAL_SHA="$(git rev-parse HEAD)" +REMOTE_SHA="$(git rev-parse "origin/${DEPLOY_BRANCH}" 2>/dev/null || echo "")" +if [[ -n "${REMOTE_SHA}" && "${LOCAL_SHA}" != "${REMOTE_SHA}" ]]; then + echo "ERROR: local ${DEPLOY_BRANCH} (${LOCAL_SHA:0:7}) != origin/${DEPLOY_BRANCH} (${REMOTE_SHA:0:7})" >&2 + echo "Run: git pull --ff-only origin ${DEPLOY_BRANCH}" >&2 + exit 1 +fi + +if [[ -z "${BUNDLE_NAME}" ]]; then + BUNDLE_NAME="jem-web-$(date +%Y%m%d-%H%M%S)" +fi +BUNDLE_DIR="${REPO_ROOT}/_deploy_bundle/${BUNDLE_NAME}" + +echo "==> JEM production bundle: ${BUNDLE_DIR}" +echo "==> Branch: ${DEPLOY_BRANCH} @ $(git rev-parse --short HEAD)" +echo "==> Pipeline (validate → derive → build)" +cd "${JEM_ROOT}" +python3 scripts/validate.py --strict +python3 scripts/validate_graph_refs.py +python3 scripts/derive.py +python3 scripts/build.py + +if [[ ! -f "${GRAPH}" ]]; then + echo "ERROR: missing ${GRAPH}" >&2 + exit 1 +fi + +COUNT="$(python3 -c "import json; print(json.load(open('${GRAPH}'))['meta'].get('entity_count', 0))")" +RELS="$(python3 -c "import json; print(json.load(open('${GRAPH}'))['meta'].get('relationship_count', 0))")" +if [[ "${COUNT}" -lt "${MIN_ENTITIES}" ]]; then + echo "ERROR: entity_count ${COUNT} < ${MIN_ENTITIES} — aborting bundle" >&2 + exit 1 +fi + +GIT_SHA="$(git rev-parse --short HEAD)" +GIT_BRANCH="$(git rev-parse --abbrev-ref HEAD)" + +rm -rf "${BUNDLE_DIR}" +mkdir -p "${BUNDLE_DIR}/public" + +echo "==> Copy web app (exclude symlink public/graph.json)" +rsync -a \ + --exclude 'public/graph.json' \ + "${JEM_ROOT}/web/" "${BUNDLE_DIR}/" + +echo "==> Materialize graph.json → public/graph.json" +cp -f "${GRAPH}" "${BUNDLE_DIR}/public/graph.json" + +cat > "${BUNDLE_DIR}/DEPLOY_README.txt" < "${REPO_ROOT}/_deploy_bundle/LATEST.txt" <&2 + exit 1 + fi + JEM_PUBLIC="${JEM_REMOTE%/}/public" + echo "==> rsync to \${JEM_REMOTE}" + rsync -avz "${BUNDLE_DIR}/public/graph.json" "${JEM_PUBLIC}/graph.json" + rsync -avz --delete \ + --exclude 'public/graph.json' \ + "${BUNDLE_DIR}/" "${JEM_REMOTE%/}/" + echo "OK: deployed to \${JEM_REMOTE}" + echo "Run smoke tests at \${JEM_PUBLIC_URL:-https://friedso.com/apps/jem/}" +fi diff --git a/jem/scripts/deploy_prep.sh b/jem/scripts/deploy_prep.sh index c1b5299..9e9652c 100755 --- a/jem/scripts/deploy_prep.sh +++ b/jem/scripts/deploy_prep.sh @@ -51,6 +51,8 @@ else fi echo "" +echo "Production deploy branch: friedso_v1 (see jem/docs/V1_RELEASE_RUNBOOK.md)" +echo "Full pipeline + bundle: ./jem/scripts/deploy_friedso_production.sh" echo "Deploy (set JEM_REMOTE to your host — do not commit real values):" echo " export JEM_REMOTE='user@your-host.example:~/path/to/apps/jem'" echo " export JEM_PUBLIC_URL='https://your-host.example/apps/jem/'"