Release 0.2.0: graduation merge race fix + VITE_APP_NAME + versioning infrastructure
- Fix §13.3 step 4 race against Gitea's async mergeability computation: gitea.wait_for_mergeable polls the mergeable field before calling merge, and bot._merge_with_retry rides out the transient 'Please try again later' response. - Frontend now requires VITE_APP_NAME at build time (frontend/vite.config.js fails the build if unset). Header brand, landing H1, and browser title read the deployment's configured name. - Establish framework versioning: VERSION (canonical) + frontend/package.json#version mirror; CHANGELOG.md as the release log with RFC 2119 / 8174 normative-language upgrade-steps; SPEC.md §20 as the binding policy; CLAUDE.md capturing the separation-of-concerns rule; docs/DEPLOYMENTS.md as the downstream operating manual; docs/QUICKSTART-VISION.md staking the future one-command-deploy capability. - Surgical fix to SPEC.md opening so the spec no longer asserts OHM is the corpus the framework produces. - SPEC.md §19.2 gains the 'Deployment-supplied subject framing' candidate that drives the 0.3.0 release.
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
# 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 install && 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 install && 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.
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,188 @@
|
||||
# Vision: a one-command path to a new rfc-app deployment
|
||||
|
||||
This document is **not** an implementation plan. It is the vision that
|
||||
the current build does not yet deliver — the experience we want for
|
||||
anyone who reads about the RFC framework and decides they'd like to
|
||||
run their own. Today, standing up a deployment is the multi-step
|
||||
journey in [`DEPLOYMENTS.md`](./DEPLOYMENTS.md). The aspiration in
|
||||
this doc is to compress that journey to a single command (or a small
|
||||
handful), so the friction between *I want to run an instance of this*
|
||||
and *I have a working instance of this* is as close to zero as we can
|
||||
get it.
|
||||
|
||||
We are writing this down now so the principles are settled before any
|
||||
code lands.
|
||||
|
||||
## Who this is for
|
||||
|
||||
The framework is the substrate for collaborative, RFC-style
|
||||
standardization of any subject where a community needs to agree on
|
||||
the meaning of its terms and where the argument that produced the
|
||||
agreement is itself worth preserving. The deployments we imagine the
|
||||
quickstart serving include — but are not limited to:
|
||||
|
||||
- **Medicine.** A public, model-assisted revision of the DSM's
|
||||
diagnostic criteria. Each diagnosis becomes an RFC; the criteria
|
||||
and their boundaries are argued in transcript; graduation gives a
|
||||
diagnosis a stable identifier and a reviewable history of how it
|
||||
came to be defined the way it is. The same shape applies to
|
||||
clinical protocols, drug-interaction taxonomies, and rare-disease
|
||||
registries.
|
||||
- **Law.** Model statutes, regulatory definitions, or clause
|
||||
libraries. An RFC per clause, with the drafting argument
|
||||
preserved alongside the final text. The framework's audit log
|
||||
doubles as legislative history.
|
||||
- **Bioethics and biosecurity.** Vaccine protocols, gene-editing
|
||||
guidelines, dual-use research review criteria. Domains where
|
||||
consensus saves lives and the reasoning behind the consensus
|
||||
matters as much as the consensus itself.
|
||||
- **Literature.** Canonical-edition standardization for works with
|
||||
contested manuscripts; lexicons for technical or constructed
|
||||
languages; controlled vocabularies for archives.
|
||||
- **Software.** Library API contracts written as prose, before the
|
||||
code. RFC-style definitions of cross-language behaviors that
|
||||
multiple implementations must respect.
|
||||
- **Policy and governance.** Municipal ordinance templates, NGO
|
||||
governance rubrics, standards-body procedural definitions.
|
||||
- **Education.** Curriculum standards, assessment rubrics, the
|
||||
meaning of competencies and credentials.
|
||||
- **Natural language itself.** The original case — the Open Human
|
||||
Model deployment — standardizing the vocabulary humans and
|
||||
machines need to share.
|
||||
|
||||
The quickstart's goal is to make the path from *"I run one of these
|
||||
communities and we need this"* to *"my community has a working
|
||||
deployment to start arguing on"* short enough that the choice is
|
||||
about the work the community wants to do, not the operational lift
|
||||
of getting started.
|
||||
|
||||
## The intended experience
|
||||
|
||||
A reader who has just finished `PHILOSOPHY.md` and decides they want
|
||||
their own deployment should be able to:
|
||||
|
||||
1. Pick a name for their deployment (one user-visible string).
|
||||
2. Run a single command from a clean machine — `npx
|
||||
create-rfc-deployment <name>`, or `docker compose up` from a tiny
|
||||
template, or `curl ... | bash`, or whatever shape the design
|
||||
settles on.
|
||||
3. Answer a small number of questions the command can't infer:
|
||||
their Gitea endpoint (or an offer to spin one up locally), an
|
||||
admin email, the OAuth callback URL.
|
||||
4. End up with: a running rfc-app instance reachable in a browser, a
|
||||
meta repository seeded with the right structure, a first sign-in
|
||||
that hands the OWNER role to the operator, and a deployment repo
|
||||
on disk holding their `.rfc-app-version` pin, their `.env`
|
||||
defaults (sans secrets), and a `CLAUDE.md` describing the
|
||||
two-repo loop.
|
||||
|
||||
From the operator's point of view, the framework should feel like
|
||||
infrastructure they instantiated, not a codebase they cloned.
|
||||
|
||||
## Why this isn't there yet
|
||||
|
||||
The current bring-up requires the operator to do work the framework
|
||||
could do for them. Specifically:
|
||||
|
||||
- **Gitea provisioning.** The operator stands up Gitea by hand,
|
||||
creates the bot account, generates the bot token, registers the
|
||||
OAuth2 application, copies credentials into `backend/.env`. A
|
||||
quickstart could spin up Gitea in a sibling container, create the
|
||||
bot via Gitea's first-run admin API, and emit the credentials
|
||||
straight into the deployment's environment.
|
||||
- **Meta-repo seeding.** Today `scripts/seed_meta_repo.py` is a
|
||||
manual step. A quickstart could run it as part of the first boot,
|
||||
detect an already-seeded repo (idempotent), and report what it did.
|
||||
- **Frontend `.env`.** The operator copies `.env.example` and hand-
|
||||
fills `VITE_APP_NAME`. A quickstart could ask once and write the
|
||||
file.
|
||||
- **Deployment repo bootstrap.** Today the deployment repo is a
|
||||
thing the operator creates manually. A quickstart could generate
|
||||
it: a fresh git repo containing `.rfc-app-version`, a README
|
||||
template, a `CONTRIBUTING.md` template, a `CLAUDE.md` template
|
||||
cloned from this repo's pattern, and a placeholder
|
||||
`rfcs/RFC-0001-...md` the operator fills in.
|
||||
- **OAuth callback wiring.** The current bring-up requires the
|
||||
operator to register an OAuth app in Gitea by hand. A quickstart
|
||||
could do this through Gitea's API once the bot token exists.
|
||||
- **Process supervision.** Today `deploy/RUNBOOK.md` walks an
|
||||
operator through systemd / nginx by hand. A quickstart could ship
|
||||
a Docker Compose file (or systemd unit templates) that bring up
|
||||
backend + frontend + Gitea in one shot.
|
||||
|
||||
Each of these is a small lift on its own. Together they're a
|
||||
quickstart product — and that's what this doc is staking out.
|
||||
|
||||
## Design principles for when the work gets picked up
|
||||
|
||||
The quickstart is downstream of every other contract in this repo.
|
||||
The principles below keep it that way:
|
||||
|
||||
**The quickstart does not embed deployment values.** It collects
|
||||
them from the operator and writes them where they belong (the new
|
||||
deployment's repo, the server's `.env`, etc.). The framework remains
|
||||
deployment-agnostic; the quickstart is a configurator, not a
|
||||
template renderer that bakes in OHM-like choices.
|
||||
|
||||
**The quickstart respects `SPEC.md` §20.** It pins the new
|
||||
deployment to the current framework version (the `VERSION` file).
|
||||
Future upgrades go through the §20.5 sequence — the quickstart does
|
||||
not invent a parallel upgrade path. If the quickstart needs to bump
|
||||
the pin, it does so by editing `.rfc-app-version` in the deployment
|
||||
repo, the same way an operator would by hand.
|
||||
|
||||
**The quickstart produces a deployment repo that an operator can
|
||||
own.** The output is plain files in a git repo, not a runtime that
|
||||
hides the configuration. An operator who never wants to think about
|
||||
the quickstart again can read the generated `CLAUDE.md` and
|
||||
`.rfc-app-version` and continue from there with `DEPLOYMENTS.md` as
|
||||
their guide.
|
||||
|
||||
**Loud failures, not silent defaults.** If the operator declines to
|
||||
supply a required value, the quickstart refuses to continue. We do
|
||||
not ship a "demo" deployment that produces something running but
|
||||
useless; the framework's separation-of-concerns rule is upstream of
|
||||
the quickstart's UX.
|
||||
|
||||
**One thing at a time.** A quickstart that tries to handle every
|
||||
deployment topology (single-machine, Kubernetes, managed Gitea,
|
||||
self-hosted Gitea, Cloud Run, Fly, etc.) on day one is a project
|
||||
that ships nothing. The first cut targets a single happy path —
|
||||
likely a single-machine Docker Compose with a local Gitea — and the
|
||||
others are §19.2-style follow-ups.
|
||||
|
||||
## Open questions to settle before building
|
||||
|
||||
When this work gets picked up, the design session will need to
|
||||
settle:
|
||||
|
||||
- **Where does the quickstart live?** A separate repo? A `tools/`
|
||||
directory in this repo? A published npm package? Each has trade-
|
||||
offs (versioning, discoverability, upgrade story).
|
||||
- **What's the shape of "one command"?** `npx create-rfc-deployment`,
|
||||
`docker compose up`, `curl | bash`, a TUI installer, a web wizard
|
||||
hosted by the framework's docs site? The shape determines the
|
||||
audience.
|
||||
- **Does the quickstart bring up Gitea or assume it exists?** Both
|
||||
are valid; "bring up Gitea" is friendlier for new operators but
|
||||
enlarges the quickstart's surface; "assume Gitea" is cleaner but
|
||||
leaves a real bring-up step in the operator's lap.
|
||||
- **How does the quickstart handle secrets?** Generate them and
|
||||
write them to disk? Print them to stdout? Both? What's the
|
||||
rotation story?
|
||||
- **What does the upgrade story look like for an operator who used
|
||||
the quickstart?** The pin lives in the deployment repo per §20.5;
|
||||
the quickstart should never become a parallel upgrade path. But
|
||||
it might offer an `rfc-app upgrade` command that just runs the
|
||||
§20.5 sequence in one shot.
|
||||
|
||||
These are the conversations to have when the time comes. None of them
|
||||
need answering today; this doc exists so that whoever picks this up
|
||||
has the shape of the goal in front of them.
|
||||
|
||||
## Status
|
||||
|
||||
Not started. This doc is the seed. When work begins, it gets its own
|
||||
§19.2 candidate in `SPEC.md` and the design session follows the
|
||||
existing decision-and-fold pattern. Until then, [`DEPLOYMENTS.md`](./DEPLOYMENTS.md)
|
||||
is the path an operator follows.
|
||||
Reference in New Issue
Block a user