Storage and audit
FORGE has its own encrypted, filesystem-shaped root in store/appfs, and it keeps exactly
two things there.
The actor audit: var/log/forge
FORGE acts on GitHub as the platform App’s bot, so GitHub cannot say which person asked for a change. The audit fills that gap. It is append-only, and each entry is:
{at, actor, action: create|change, app, kind, brief_sha256}actoris the email CORE vouched for.- The brief is stored only as a hash. The issue holds the brief verbatim, and the audit does not copy GitHub state.
- An entry is written before the GitHub call, and the call is refused if the entry
cannot be written:
could not record this request in the forge audit log, so it was not sent: …. No FORGE action goes unattributed. - An approval records
createfor a new project, andchangefor each brief and each manifest change.ProposePlanrecords nothing, because it changes nothing.
The project chats: home/<id>/chats/<chat>
Each chat is one sealed file per person. The <id> is the first 32 hex digits of the
SHA-256 of the person’s email. A file holds the title, the project name, the lanes and
their components, and the transcript. Read-modify-writes are serialised. Another person’s
chats are never listed or read. Limits: 500 chats per person, and 400 messages of at most
8000 bytes each per chat.
Sealing and placement
- The root is sealed under a key derived with HKDF from
CORE_ENVELOPE_KEK(labelrunink/appfs/forge). There is no plaintext mode. - If
OBJECTSTORE_ENDPOINTis set, the root lives on objectd in bucketforge. Otherwise it uses the local encrypted fallback inFORGE_APPFS_DIR. If the object store is set but broken, the root does not mount; it never falls back to local disk silently. - A pod must mount a persistent volume at
FORGE_APPFS_DIR. On anemptyDir, every restart loses the audit and the chats.
When the root cannot mount, the server still starts and logs
appfs not mounted: … — forge mutations and chats will refuse until it is. Chats and
approvals then answer FailedPrecondition. WhoAmI and the ForgeService reads still work.
What is never stored
- Anything built. Repos, briefs and verification are read live from GitHub.
- The GitHub installation token. It is a one-hour credential, re-minted from the App key and kept in memory only.
- Plans. They are kept in memory for one hour, and a restart forgets them.
- Credentials of any kind in a lane’s settings. Secret-looking keys are refused, and a connector stores a CORE connection id.
Sources
grpc/internal/appstate/appstate.go; grpc/internal/audit/audit.go;
grpc/internal/chats/store.go; grpc/cmd/serve.go (openAppState); CLAUDE.md
(“The forge audit (appfs)”).