Skip to content
Storage and audit

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}
  • actor is 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 create for a new project, and change for each brief and each manifest change. ProposePlan records 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 (label runink/appfs/forge). There is no plaintext mode.
  • If OBJECTSTORE_ENDPOINT is set, the root lives on objectd in bucket forge. Otherwise it uses the local encrypted fallback in FORGE_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 an emptyDir, 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)”).