21fcbc92d4
Adds an email allowlist (toggleable per deployment) that restricts OAuth sign-in to listed emails while keeping read paths public. Anonymous visitors now see the full app shell in read-only mode instead of the §14.1 landing wall. Empty allowlist = gate off, so deployments that don't enable it behave exactly as 0.2.3. Also fixes single-finger scroll on /philosophy and other .chrome-pane views on iOS Safari (.app: 100vh → 100dvh). Renames deploy/nginx/rfc.wiggleverse.org.conf → ohm.wiggleverse.org.conf to match the deployed-domain rename (rfc.wiggleverse.org deprovisioned 2026-05-27). See CHANGELOG.md for full details + upgrade steps. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
283 lines
12 KiB
Markdown
283 lines
12 KiB
Markdown
# Deployments — building on rfc-app and keeping in sync
|
|
|
|
This document is the operating manual for downstream projects that
|
|
host an instance of the RFC framework. It is the practical
|
|
counterpart to [`SPEC.md` §20](../SPEC.md), which carries the
|
|
binding policy. If anything here disagrees with §20, §20 wins and
|
|
the discrepancy is a bug in this doc.
|
|
|
|
The audience is the operator of a downstream deployment — someone
|
|
who wants to run their own corpus on top of rfc-app and keep that
|
|
deployment current as the framework releases new versions.
|
|
|
|
## What is a "deployment"?
|
|
|
|
A deployment is a running instance of the framework, hosting a
|
|
specific corpus, branded and configured for a specific community.
|
|
Each deployment has:
|
|
|
|
- **A content repo** (separate from this one). Typically holds the
|
|
initial RFC markdown files, a `README.md` describing what the
|
|
deployment is, a `CONTRIBUTING.md`, and a `.rfc-app-version`
|
|
pin. The Open Human Model (`ohm-rfc`) is one such repo.
|
|
- **A Gitea instance** the framework talks to. Hosts the meta repo
|
|
and the per-RFC repositories the graduation flow creates.
|
|
- **A server** running the rfc-app backend + frontend bundle,
|
|
pointed at the Gitea instance, with `frontend/.env` and
|
|
`backend/.env` configured for this deployment.
|
|
- **A pin** — the `.rfc-app-version` file in the deployment's
|
|
content repo, declaring which framework version is currently in
|
|
flight.
|
|
|
|
The framework repo (this one) is generic across deployments. The
|
|
deployment supplies the values, the content, and the operational
|
|
choices. The framework supplies the substrate.
|
|
|
|
## Starting a new deployment
|
|
|
|
The shortest path from scratch:
|
|
|
|
1. **Create the deployment's content repo.** Anywhere git lives —
|
|
GitHub, Gitea, GitLab. It will hold the initial RFC markdown
|
|
files and the deployment's public-facing README. Add a
|
|
`.rfc-app-version` file with the framework version you intend to
|
|
pin to (the current `VERSION` from this repo, or any prior
|
|
version if you have a reason to pin behind).
|
|
|
|
2. **Stand up Gitea.** Anything that exposes the Gitea REST API
|
|
works; `deploy/RUNBOOK.md` in this repo describes the bring-up.
|
|
Create the bot service account and the OAuth2 application.
|
|
|
|
3. **Check out rfc-app at the version your deployment pins to.**
|
|
`git checkout` the tag (or commit) matching the
|
|
`.rfc-app-version` you chose. The pin is a contract — your
|
|
deployment's behavior is defined by *that* version's code, not
|
|
`main`.
|
|
|
|
4. **Configure the backend.** Copy `backend/.env.example` to
|
|
`backend/.env` and fill in the Gitea URL, bot token, OAuth
|
|
client credentials, `GITEA_ORG`, `META_REPO`, OWNER login, and
|
|
any optional providers (SMTP, LLM keys). The `.env.example` is
|
|
the contract for what the framework expects at this version;
|
|
read every entry.
|
|
|
|
5. **Configure the frontend.** Copy `frontend/.env.example` to
|
|
`frontend/.env` and fill it in. At minimum, every release
|
|
requires `VITE_APP_NAME` (the user-visible name of your
|
|
deployment — what it gets called in the browser tab, the header
|
|
brand, and the landing H1). The framework ships no defaults for
|
|
any deployment-identifying value; if a required variable is
|
|
missing, the build fails loudly.
|
|
|
|
6. **Build and run.** `cd frontend && npm ci && npm run
|
|
build`, then start the backend per `deploy/RUNBOOK.md`. The
|
|
first sign-in is the OWNER login from `backend/.env`.
|
|
|
|
7. **Seed the meta repo.** `python scripts/seed_meta_repo.py`
|
|
creates the meta repo on Gitea with the initial README,
|
|
CONTRIBUTING, and `.gitignore` per §1–§2. This step is one-time
|
|
per deployment.
|
|
|
|
8. **Bring in your content.** Open proposal PRs against the meta
|
|
repo to add your initial RFCs, or — if you're migrating an
|
|
existing corpus — write directly into the meta repo's `rfcs/`
|
|
directory at the version of `.rfc/metadata.yaml` your pinned
|
|
framework version expects.
|
|
|
|
At this point the deployment is running. The content repo holds
|
|
the canonical text and the `.rfc-app-version` pin; the server runs
|
|
the framework at that pin against your Gitea; the framework knows
|
|
nothing about your specific deployment beyond what your `.env`
|
|
files told it.
|
|
|
|
## The two-repo working pattern
|
|
|
|
Once a deployment is live, day-to-day changes split across the
|
|
framework repo and the deployment repo. The triage that decides
|
|
which side a given change lands on:
|
|
|
|
A change is **deployment-side** when it is about a value, a piece
|
|
of content, or an operational choice specific to this instance.
|
|
Examples: changing the display name; updating an RFC's body;
|
|
adding a new RFC; rotating Gitea credentials; tightening SMTP
|
|
settings; changing who has the OWNER role.
|
|
|
|
A change is **framework-side** when it is about behavior, UI
|
|
logic, schema, or any piece of code that would matter the same way
|
|
to a different deployment. Examples: fixing a bug; adding a new
|
|
feature; making a previously hardcoded value configurable; tuning
|
|
a retry; correcting a race condition; updating a dependency that
|
|
affects observable behavior.
|
|
|
|
The most common case is **mixed**: the framework needs to make
|
|
something configurable, and the deployment needs to supply the
|
|
value. The change pattern is then: framework adds the env var (or
|
|
config knob) and the loud-failure path, ships a new minor or
|
|
patch; the deployment upgrades to the new version and supplies
|
|
the value in its `.env`.
|
|
|
|
When picking up a change, name it in deployment terms first ("in
|
|
*my deployment* I want X"), then triage. The phrasing keeps the
|
|
work honest — every framework change is in service of something
|
|
someone wants from their deployment.
|
|
|
|
## Upgrading to a new framework version
|
|
|
|
The upgrade contract is in [`SPEC.md` §20.5](../SPEC.md), and the
|
|
normative language used in upgrade-steps blocks is fixed in §20.4.
|
|
The mechanics in practice:
|
|
|
|
1. **Decide the target version.** Read `CHANGELOG.md` from the
|
|
current pin up through `main`. Pick the target — usually the
|
|
latest, but pinning behind is legitimate if a release introduces
|
|
something the deployment isn't ready for.
|
|
|
|
2. **Read every intervening release's upgrade steps.** Each release
|
|
header in `CHANGELOG.md` includes an "upgrade steps" block when
|
|
there is operator action required. Read them in order; the
|
|
composition is your upgrade plan. **Cross-version upgrades are
|
|
not pre-computed** (see §20.4) — going from 0.1.0 to 0.5.0 means
|
|
walking the upgrade-steps for 0.2.0, then 0.3.0, then 0.4.0,
|
|
then 0.5.0 in that order, applying them as a single plan.
|
|
|
|
3. **Triage the plan by normative language.** Each step is worded
|
|
per RFC 2119 / 8174:
|
|
- **MUST / SHALL / REQUIRED** steps are non-negotiable. Skip one
|
|
and the deployment is out of contract on the new version.
|
|
Either it will fail to build or start (the framework's
|
|
§20.6 commitment to loud failures), or it will misbehave
|
|
subtly later. Treat these as the binding part of your plan.
|
|
- **SHOULD / RECOMMENDED** steps are the framework's tested
|
|
default. You can deviate when you have a reason, but
|
|
subsequent releases assume you took the recommended path; a
|
|
deviation now may complicate a future upgrade.
|
|
- **MAY / OPTIONAL** steps are affordances. Take them or skip
|
|
them based on your needs; the framework's behavior doesn't
|
|
depend on the choice.
|
|
- **MUST NOT / SHOULD NOT** steps remove or discourage prior
|
|
behavior. Check whether your deployment was relying on the
|
|
called-out behavior; if so, that's the thing to change.
|
|
|
|
4. **Apply the upgrade steps in order against the deployment's
|
|
environment.** New env vars get added to `frontend/.env` or
|
|
`backend/.env`. Renamed vars get renamed. Removed vars get
|
|
removed. Migration scripts (if any are referenced) get run.
|
|
|
|
5. **Check out the framework at the target version.** `git
|
|
fetch && git checkout <tag>`.
|
|
|
|
6. **Rebuild.** `npm ci && npm run build` for the frontend;
|
|
restart the backend.
|
|
|
|
7. **Smoke-test.** Sign in, check the brand reflects your
|
|
`VITE_APP_NAME`, exercise whatever surface the release touched.
|
|
Walk through the SHOULD steps that involve verification.
|
|
|
|
8. **Update the pin.** Change `.rfc-app-version` in the deployment
|
|
repo to the target version. Commit. This is the deployment
|
|
promising its users that *this* is the framework version
|
|
currently in flight.
|
|
|
|
If a step fails, stop. Do not bump the pin. The framework's
|
|
obligation per §20.6 is that a faithful upgrade does not regress
|
|
the deployment — failure is a bug to report against the framework
|
|
release, not something to paper over.
|
|
|
|
### Composing across multiple versions
|
|
|
|
Worked example: a deployment pinned to `0.1.0` wants to move to
|
|
some future `0.4.0`. The plan is the concatenation of three
|
|
adjacent upgrade-steps blocks from `CHANGELOG.md`:
|
|
|
|
```
|
|
0.1.0 → 0.2.0: MUST set VITE_APP_NAME, MUST rebuild, SHOULD verify
|
|
0.2.0 → 0.3.0: (whatever the 0.3.0 entry's steps are)
|
|
0.3.0 → 0.4.0: (whatever the 0.4.0 entry's steps are)
|
|
```
|
|
|
|
The operator concatenates these into one plan, applies them in
|
|
order, rebuilds once at the end against `0.4.0`'s checkout, and
|
|
bumps the pin to `0.4.0`. No path-specific instructions are
|
|
needed because each adjacent step is unambiguous in isolation.
|
|
|
|
This is why §20.4 binds the normative-language convention: the
|
|
composition only works mechanically if each adjacent step's
|
|
strength is clear from its wording.
|
|
|
|
## Versioning, briefly
|
|
|
|
[`SPEC.md` §20.2](../SPEC.md) is the binding rule. Summary:
|
|
|
|
- **Patch** (`0.x.0 → 0.x.1`): redeploy and you're done. No env
|
|
changes, no behavior surprises.
|
|
- **Minor** (`0.x.0 → 0.(x+1).0`): read the changelog. New optional
|
|
config, new features, and — while pre-1.0 — breaking changes
|
|
that the changelog spells out.
|
|
- **Major** (`0.x.0 → 1.0.0`, then `1.0.0 → 2.0.0`): always read
|
|
the changelog. Breaking by definition; upgrade steps required.
|
|
|
|
The framework is pre-1.0 today. Expect minor bumps to occasionally
|
|
require changes to your `.env` files until the first stable cut.
|
|
|
|
## The deployment's repo, at a glance
|
|
|
|
A deployment's content repo typically holds:
|
|
|
|
- The RFC markdown files (one per concept the deployment defines).
|
|
- A `README.md` describing what the deployment is, public-facing.
|
|
- A `CONTRIBUTING.md` describing how to participate.
|
|
- `.rfc-app-version` — the framework pin (single line, SemVer).
|
|
- A `CLAUDE.md` describing how to work on the deployment, the
|
|
two-repo iteration pattern, and the deployment-specific values
|
|
(typically without secrets — those live on the server only).
|
|
|
|
The deployment repo does **not** hold:
|
|
|
|
- Any of the framework's code. The framework lives in its own repo;
|
|
you build against it at the pinned version.
|
|
- Secrets. `.env` files belong on the server, never in git.
|
|
- Forked copies of framework files. If you find yourself wanting to
|
|
edit a framework file, the right move is to file a change against
|
|
the framework, get a release, and pin to it.
|
|
|
|
## Private-beta gate
|
|
|
|
From `0.3.0` onward, every deployment ships with an opt-in email
|
|
allowlist. The default state is **off**: an empty `allowed_emails`
|
|
table behaves exactly like 0.2.x — any successful Gitea OAuth
|
|
provisions a user.
|
|
|
|
To run a closed beta:
|
|
|
|
1. Sign in once as the deployment operator so your `users` row exists
|
|
(you will be grandfathered by `gitea_id` thereafter — adding the
|
|
first allowlist row does **not** lock you out).
|
|
2. Open `/admin/allowlist` and add the first invited email. As soon as
|
|
any row exists, sign-in is restricted to listed emails plus
|
|
grandfathered users.
|
|
3. Optionally set `VITE_BETA_CONTACT` in `frontend/.env` (an email
|
|
address, a URL, or a short instruction). It is shown to rejected
|
|
sign-ins on the `/beta-pending` page so visitors know how to
|
|
request an invitation.
|
|
|
|
To re-open the deployment, remove all rows from `allowed_emails` (the
|
|
admin tab has a Remove button per row) — the gate flips off as soon
|
|
as the last row is gone.
|
|
|
|
Anonymous viewers see the full app in read-only mode regardless of
|
|
allowlist state. Write affordances (Propose, chat, Contribute, Open
|
|
PR) are hidden behind a sign-in CTA, and the public read endpoints
|
|
behave the same in both states.
|
|
|
|
## When something goes wrong
|
|
|
|
If a framework behavior is wrong for your deployment, file it as a
|
|
framework change request: a clear statement of what you wanted, what
|
|
happened instead, and (if you can) what the right separation of
|
|
concerns is between framework and deployment.
|
|
|
|
If a framework upgrade breaks your deployment after you followed the
|
|
changelog faithfully, that's a §20.6 violation — file it against the
|
|
framework release. The pin lets you roll back to the prior version
|
|
while the framework fixes the regression.
|