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>
12 KiB
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, 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.mddescribing what the deployment is, aCONTRIBUTING.md, and a.rfc-app-versionpin. 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/.envandbackend/.envconfigured for this deployment. - A pin — the
.rfc-app-versionfile 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:
-
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-versionfile with the framework version you intend to pin to (the currentVERSIONfrom this repo, or any prior version if you have a reason to pin behind). -
Stand up Gitea. Anything that exposes the Gitea REST API works;
deploy/RUNBOOK.mdin this repo describes the bring-up. Create the bot service account and the OAuth2 application. -
Check out rfc-app at the version your deployment pins to.
git checkoutthe tag (or commit) matching the.rfc-app-versionyou chose. The pin is a contract — your deployment's behavior is defined by that version's code, notmain. -
Configure the backend. Copy
backend/.env.exampletobackend/.envand 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.exampleis the contract for what the framework expects at this version; read every entry. -
Configure the frontend. Copy
frontend/.env.exampletofrontend/.envand fill it in. At minimum, every release requiresVITE_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. -
Build and run.
cd frontend && npm ci && npm run build, then start the backend perdeploy/RUNBOOK.md. The first sign-in is the OWNER login frombackend/.env. -
Seed the meta repo.
python scripts/seed_meta_repo.pycreates the meta repo on Gitea with the initial README, CONTRIBUTING, and.gitignoreper §1–§2. This step is one-time per deployment. -
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.yamlyour 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, and the
normative language used in upgrade-steps blocks is fixed in §20.4.
The mechanics in practice:
-
Decide the target version. Read
CHANGELOG.mdfrom the current pin up throughmain. Pick the target — usually the latest, but pinning behind is legitimate if a release introduces something the deployment isn't ready for. -
Read every intervening release's upgrade steps. Each release header in
CHANGELOG.mdincludes 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. -
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.
-
Apply the upgrade steps in order against the deployment's environment. New env vars get added to
frontend/.envorbackend/.env. Renamed vars get renamed. Removed vars get removed. Migration scripts (if any are referenced) get run. -
Check out the framework at the target version.
git fetch && git checkout <tag>. -
Rebuild.
npm ci && npm run buildfor the frontend; restart the backend. -
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. -
Update the pin. Change
.rfc-app-versionin 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 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, then1.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.mddescribing what the deployment is, public-facing. - A
CONTRIBUTING.mddescribing how to participate. .rfc-app-version— the framework pin (single line, SemVer).- A
CLAUDE.mddescribing 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.
.envfiles 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:
- Sign in once as the deployment operator so your
usersrow exists (you will be grandfathered bygitea_idthereafter — adding the first allowlist row does not lock you out). - Open
/admin/allowlistand add the first invited email. As soon as any row exists, sign-in is restricted to listed emails plus grandfathered users. - Optionally set
VITE_BETA_CONTACTinfrontend/.env(an email address, a URL, or a short instruction). It is shown to rejected sign-ins on the/beta-pendingpage 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.