# RFC: Per-Operator Config — the Operator Slice of RFC-002
**Status:** Proposed
**Date:** 2026-06-11
**Builds on:** [rfc-002-config-cli-architecture.md](rfc-002-config-cli-architecture.md) (Proposed; implementation parked — PRs #139/#162 closed over review findings), [rfc-005-server-cluster-boot.md](rfc-005-server-cluster-boot.md) (Landed), RFC-006 storage roots (#186/#190/#194, landed). The #139 review record is a normative input: every design rule in §D6 traces to a confirmed finding.
**Target release:** unversioned (staged; see Sequencing).
## Summary
Give OmniGraph the operator half of the Terraform config split. Terraform
separates `~/.terraformrc` (who I am, my credentials, my CLI behavior) from
the working directory's `*.tf` (what the project declares). OmniGraph today
has only the project half: `./omnigraph.yaml` in the current working
directory (or `--config <path>`), and nothing else — no home-level config,
no walk-up, no env override for the CLI. Operator identity and credentials
must be re-declared in every directory an operator works from, and — worse —
they end up in files that live next to repo-committed project config.
This RFC introduces **`~/.omnigraph/config.yaml`** (the operator layer) and
a **keyed credentials chain**, scoped deliberately small:
1.**Operator identity** — a default actor for every `--as` cascade.
2.**Credentials by server name** — no more inventing env-var names per
server; secrets never inline, never in the project layer.
3.**Named servers** — operator-owned endpoint definitions that project
configs can reference but not redefine.
It is explicitly a **subset of RFC-002**, sequenced to land. RFC-002 settled
the right long-term decisions (one `~/.omnigraph/` dir, credentials keyed by
server name, `OMNIGRAPH_CONFIG`/`OMNIGRAPH_HOME` env precedence) but its
implementation arrived as one 4,800-line PR mixing a crate extraction with
behavior changes, and died over ten confirmed findings. This RFC adopts
RFC-002's settled decisions verbatim where they apply, defers everything
else (`GraphLocator`, multi-homing, `omnigraph use`, the State layer), and
encodes the #139 findings as design rules so the same failures cannot recur.
## Motivation
Three concrete pains, all hit in real operation this cycle:
- **Identity repetition.** The cluster actor cascade (#180) resolves
`--as` from the per-operator `omnigraph.yaml` — which means every
operator hand-maintains a copy in every working directory (the
`~/exp/intel` setup needed exactly this). A repo-committed
`omnigraph.yaml` cannot carry `as: act-andrew` without claiming every
contributor is Andrew.
- **Credential ergonomics.** `bearer_token_env` forces three coordinated
steps per server (invent a var name, reference it in config, set it in
the secret store). The peer group — AWS profiles, `gh hosts`, kubeconfig
users — keys secrets by the server's *name*.
- **Cluster-era working shape.** With clusters on object storage (RFC-006),
the project directory is a *declaration checkout* — operators run
`cluster apply --config ./checkout` from anywhere. The things that are
about the *operator* (who am I, which servers do I know, how do I like
output formatted) have no home that travels with them.
## Non-Goals
- **`GraphLocator` / multi-homed graph resolution** (RFC-002 §1) — the
biggest and riskiest part of config-v2; untouched here.
- **`omnigraph use` / the State layer** (`~/.omnigraph/state/`) — deferred
with it (finding #2 showed its precedence interacts badly with scaffolds;
that problem belongs to the slice that introduces it).
- **OS keychain integration** — the credentials *chain* (§D4) leaves a slot
for it; this RFC ships env + file sources only.
- **Project-file walk-up.** Terraform does not walk up from subdirectories
and neither do we — `--config` (or running in the directory) stays the
explicit, deterministic story. Rejected, not deferred: walk-up makes "which
config am I using" a function of cwd depth, the class of surprise this RFC
exists to remove.
- **Renaming or removing anything.** No flag renames, no key renames, no
2.`[<name>]` section in `~/.omnigraph/credentials` (INI-style, `0600`;
the loader refuses a group/world-readable file).
3. The legacy pair — `bearer_token_env` + `auth.env_file` — exactly as
today, for configs that already use it.
No inline secrets in any YAML file, operator or project (the existing
invariant 12 posture extended to disk). A future `omnigraph login <name>`
writes/rotates one section of the credentials file via temp + rename
(finding #7: every operator-layer write is atomic), creating it `0600`.
### D5. The trust boundary (the security findings, made structural)
Findings #4, #5, #6 share one root cause: the project layer — a file that
arrives with a *repo checkout* — could redirect where requests go and what
secrets they carry. The rules:
1. **A project file may *reference* a server by name; it may not *redefine*
an operator-defined server.** If `./omnigraph.yaml` declares
`servers.prod.url` and `~/.omnigraph/config.yaml` also defines `prod`,
the operator definition wins and the CLI warns about the shadowed
project entry. A project-only server name keeps working (legacy compat),
but the keyed-credentials chain (§D4 steps 1–2) never resolves for it —
only the legacy explicit `bearer_token_env` does. Net effect: a malicious
checkout cannot point `prod` at an attacker host and harvest the
operator's `prod` token.
2.**`auth.env_file` keeps auto-loading (compat), but project-layer
env-files cannot *override* variables already set in the process or by
the operator layer** — first-set-wins, operator-before-project (the
existing real-env-wins rule, extended one layer down). Finding #5's
injection becomes a no-op against any var the operator actually uses.
3.**A token is sent only to the server it is keyed to.** The legacy
single `OMNIGRAPH_BEARER_TOKEN` fallback keeps working for the
single-server shape, but when a request resolves through a *named*
server, only that name's chain applies (finding #6's broadcast).
### D6. Compatibility rules (the #139 findings as law)
| Rule | Source finding |
|---|---|
| No flag or key is removed or renamed; new behavior is additive | #1, #3 |
| A config that loads today loads identically after this RFC; new validation applies only to new keys | #3, #8, #10 |
| Every operator-layer file write is temp + rename, never in-place | #7 |
| `~` expands wherever a path is read | #9 |
| Map merges are per-entry, per-field — never wholesale replace | #13 |
| One resolution path per concern — the actor chain and the token chain each have exactly one implementation, called by CLI and server alike | #11, #12 |
| Each slice lands as its own PR with the workspace gate green; no slice mixes mechanical moves with behavior changes | #139's disposition |
## Sequencing
Three PRs, each independently useful, each landable without the next:
1.**PR 1 — the operator file + identity.** Loader for