GitHub is the database
The backend is stateless about what is built. It creates repos, files briefs verbatim and reads status, but it never mirrors any of that somewhere it could drift. Three places hold state:
- GitHub holds what is built: repos, work orders and verification.
- FORGE’s appfs root holds the chats and the actor audit. See Storage and audit.
- CORE holds sign-in.
Repositories
A project’s repository is private, created in the organisation (FORGE_GITHUB_ORG,
default org-runink), and tagged with these topics:
| Topic | Meaning |
|---|---|
forged | FORGE made this repo. ListApps and the imported chats read this topic. |
forge-project | A multi-lane project that carries forge.yaml |
forge-web / forge-pipeline | One topic per kind its lanes use. For a single-kind app this is the only record of its kind. A forged repo with no kind topic reads as web. |
The first commit is one parentless commit made over the Git Data API (blobs, then
tree, commit and ref). It replaces GitHub’s auto-init commit, and the default branch is
force-moved to it. A project repo is always seeded from the embedded skeletons. Only
the older single-app path (ForgeService.CreateApp) uses GitHub’s /generate instead,
and only when a template repo is configured (FORGE_TEMPLATE_REPO or
FORGE_TEMPLATE_REPO_PIPELINE). None is configured today.
Work orders
A brief is filed as an issue:
- Title:
forge: <lane name> — <first line of the brief, up to 60 characters> - Labels:
forge,kind:<kind>andlane:<id> - Body: two HTML-comment headers, one visible line scoping the work to the lane’s
folder, a
---separator, and then the brief verbatim:
<!-- forge-kind: pipeline -->
<!-- forge-lane: ingest path: pipelines/ingest -->
This brief owns the folder `pipelines/ingest/` (lane "Ingest"). Read `pipelines/ingest/AGENTS.md` for its stack contract, and change nothing outside that folder.
---
<the brief, exactly as approved>FORGE works out which lane an issue belongs to from its lane: label first and its
forge-lane header second. It recovers the brief exactly by taking everything after the
first --- that follows the headers. An app from before projects has only the
forge-kind header. Its issues all belong to its one lane.
Iterations
ListIterations is the MVP loop’s history for one app: one iteration per forge issue,
open or closed, oldest first. The oldest is marked initial. Each iteration carries the
core/ci status of the newest default-branch commit dated inside its window (from its
issue to the next issue). That verification followed the brief. It was not necessarily
caused by it, because GitHub links no commit to an issue. Reads are bounded: 500 issues,
100 commits, and at most 3 status reads per window and 20 per call. Past a cap an
iteration reads as having no core/ci.
Reads are shared briefly and never guessed
Opening a chat calls GetChat and GetProject at the same time. Their shared GitHub
reads happen once and are kept for 10 seconds. The studio shows when the oldest answer
was read (“GitHub read HH:MM:SS”). Re-read from GitHub, Re-read now and Try
again force a fresh read. Every approval reads fresh, failures are never cached, and
every write drops the repo’s cached entries.
When GitHub cannot be read, the answer says so. The chat’s status_error or the list’s
imports_error is set, the lanes are still drawn, and their status is absent, never
guessed. GitHub’s own error text passes through unparaphrased. For example,
Resource not accessible by integration is how an operator learns the App lacks a
permission.
Sources
grpc/internal/ghforge/{ghforge,project,kind,iterations,coreci}.go;
grpc/cmd/{hub_cache,iterations_server,chat_server}.go.