Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions .github/PUBLISH_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
- 'jem/scripts/**'
- '.github/workflows/validate.yml'
push:
branches: [main]
branches: [main, friedso_v1]
paths:
- 'jem/data/**'
- 'jem/scripts/**'
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**.

Expand Down
10 changes: 9 additions & 1 deletion jem/docs/SESSION_WORKFLOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

---

Expand Down
28 changes: 25 additions & 3 deletions jem/docs/V1_RELEASE_RUNBOOK.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
```

Expand Down
178 changes: 178 additions & 0 deletions jem/scripts/deploy_friedso_production.sh
Original file line number Diff line number Diff line change
@@ -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" <<EOF
JEM production deploy bundle
Generated: $(date -u +%Y-%m-%dT%H:%M:%SZ)
Git: ${GIT_BRANCH} @ ${GIT_SHA}
graph.json: entity_count=${COUNT} relationship_count=${RELS}

Deploy branch: ${DEPLOY_BRANCH} (https://friedso.com/apps/jem/)

--- rsync (set JEM_REMOTE in your shell; do not commit) ---

export JEM_REMOTE='user@your-host:~/path/to/apps/jem'
export JEM_PUBLIC="\${JEM_REMOTE}/public"

rsync -avz public/graph.json "\${JEM_PUBLIC}/graph.json"
rsync -avz --delete --exclude 'public/graph.json' ./ "\${JEM_REMOTE}/"

--- smoke test ---

export JEM_PUBLIC_URL='https://friedso.com/apps/jem/'
See jem/docs/V1_RELEASE_RUNBOOK.md §2
EOF

python3 -c "
import json
from pathlib import Path
g = json.loads(Path('${GRAPH}').read_text())
manifest = {
'built_at': '$(date -u +%Y-%m-%dT%H:%M:%SZ)',
'git_sha': '${GIT_SHA}',
'git_branch': '${GIT_BRANCH}',
'deploy_branch': '${DEPLOY_BRANCH}',
'entity_count': g.get('meta', {}).get('entity_count'),
'relationship_count': g.get('meta', {}).get('relationship_count'),
'graph_version': g.get('meta', {}).get('version'),
}
Path('${BUNDLE_DIR}/bundle_manifest.json').write_text(json.dumps(manifest, indent=2))
"

cat > "${REPO_ROOT}/_deploy_bundle/LATEST.txt" <<EOF
${BUNDLE_NAME}
${BUNDLE_DIR}
branch=${DEPLOY_BRANCH}
entities=${COUNT}
EOF

echo ""
echo "OK: bundle ready at"
echo " ${BUNDLE_DIR}"
echo " branch=${DEPLOY_BRANCH} entities=${COUNT} relationships=${RELS}"
echo ""
echo "Next: smoke test per jem/docs/V1_RELEASE_RUNBOOK.md §2"
echo " export JEM_PUBLIC_URL='https://friedso.com/apps/jem/'"
echo ""

if [[ "${DO_DEPLOY}" -eq 1 ]]; then
if [[ -z "${JEM_REMOTE:-}" ]]; then
echo "ERROR: --deploy requires JEM_REMOTE in environment" >&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
2 changes: 2 additions & 0 deletions jem/scripts/deploy_prep.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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/'"
Expand Down
Loading