Merge pull request #195 from ModernRelay/rfc/operator-config

docs(rfc): RFC-007 + RFC-008 — the config architecture pair (operator layer; deprecate omnigraph.yaml)
This commit is contained in:
Andrew Altshuler 2026-06-11 21:01:54 +03:00 committed by GitHub
commit d5d703fccc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 468 additions and 0 deletions

View file

@ -76,6 +76,8 @@ Working documents for in-flight feature work. Removed when the work lands.
| Future cluster control plane — declarative as-code config, JSON state ledger, reconciler | [cluster-config-specs.md](cluster-config-specs.md), [cluster-axioms.md](cluster-axioms.md), [cluster-config-implementation-spec.md](cluster-config-implementation-spec.md) |
| Cluster graph & schema apply — Phase 4 sidecars, roll-forward recovery, approval artifacts | [rfc-004-cluster-graph-schema-apply.md](rfc-004-cluster-graph-schema-apply.md) |
| Server boots from cluster state — Phase 5 mode switch, applied-revision serving | [rfc-005-server-cluster-boot.md](rfc-005-server-cluster-boot.md) |
| Per-operator config — `~/.omnigraph/` identity, keyed credentials, named servers (the operator slice of RFC-002) | [rfc-007-operator-config.md](rfc-007-operator-config.md) |
| Deprecate `omnigraph.yaml` — one concern per config surface; key-by-key migration map and staged retirement | [rfc-008-deprecate-omnigraph-yaml.md](rfc-008-deprecate-omnigraph-yaml.md) |
## Boundary

View file

@ -0,0 +1,292 @@
# 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.
**Paired with:** [rfc-008-deprecate-omnigraph-yaml.md](rfc-008-deprecate-omnigraph-yaml.md) — together they define the two-surface architecture this RFC's operator half belongs to.
**Target release:** unversioned (staged; see Sequencing).
## Summary
Give OmniGraph the operator half of the **two-surface config architecture**
(RFC-008): **cluster config** (team-owned, in a repo — what the system *is*)
and **operator config** (person-owned, in `$HOME` — who *I* am). This is
Terraform's split: `~/.terraformrc` for the operator, the checkout for the
declaration. OmniGraph today has neither half cleanly — `omnigraph.yaml`
mixes both concerns (RFC-008 retires it), and there is no home-level config
at all: identity and credentials get re-declared per working directory, in
files that sit next to repo-committed config.
This RFC introduces **`~/.omnigraph/config.yaml`** (the operator surface)
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 any repo-committed file.
3. **Named servers** — operator-owned endpoint definitions; nothing a
checkout supplies can redefine them.
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.
- **Config-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 for cluster checkouts. 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.
- **Retiring `omnigraph.yaml`** — that is RFC-008's job, with its own
staging. This RFC builds the destination; during RFC-008's deprecation
window the legacy file keeps loading exactly as today.
- **Renaming or removing anything.** No flag renames, no key renames, no
schema-version bumps (findings #1, #3, #10).
## Background (verified against main)
- **Project-config lookup today** (`crates/omnigraph-server/src/config.rs:529-553`,
shared by CLI and server): `--config <path>`, else `./omnigraph.yaml` in
cwd, else built-in defaults. Relative paths inside the file resolve
against the file's own directory (`base_dir`). No env var, no home file,
no walk-up.
- **Side-effect on load** (`crates/omnigraph-cli/src/helpers.rs:102-108`):
`load_cli_config` also loads `auth.env_file` into the process env —
this is how `OMNIGRAPH_BEARER_TOKEN` reaches remote commands today.
- **Actor resolution** (`helpers.rs:170`, #180): `--as` flag, else the
project config's actor — currently the end of the chain.
- **Existing credential mechanism**: `TargetConfig.bearer_token_env` names
an env var; `auth.env_file` points at a git-ignored dotenv. Both keep
working indefinitely (RFC-002 already committed to this; finding #3
showed what happens otherwise).
- **`OMNIGRAPH_CONFIG`** exists today only as the *container entrypoint's*
translation to the server's `--config`. The CLI does not read it.
## Design
### D1. Files and discovery
```
~/.omnigraph/config.yaml # the operator surface (this RFC)
~/.omnigraph/credentials # keyed secrets, 0600, git-irrelevant (§D4)
./cluster.yaml + checkout # the team surface (unchanged; RFC-004..006)
./omnigraph.yaml # legacy, loads as today through RFC-008's window
```
Discovery order for the operator file: `$OMNIGRAPH_HOME/config.yaml` if
`OMNIGRAPH_HOME` is set, else `~/.omnigraph/config.yaml`. Absent file =
empty layer, never an error. `~` is expanded wherever paths are read
(finding #9 — today a literal `./~/...` directory gets created).
`OMNIGRAPH_CONFIG=<path>` becomes a first-class override for the `--config`
argument in the CLI (highest precedence below the flag itself), aligning the
CLI with the container contract that already uses this variable for the
server. One name, one meaning, both binaries — it points at whatever the
command's `--config` would (a cluster checkout for cluster commands; the
legacy file during RFC-008's window).
Per RFC-002 §4 (adopted verbatim): `~/.omnigraph/` is the one canonical
dir — cache/state subdirectories arrive with their own slices; XDG roots are
not part of the mental model (`$XDG_CONFIG_HOME` may be honored as a
fallback read location if set, but is never written to).
### D2. The operator schema (v1 of this layer)
```yaml
# ~/.omnigraph/config.yaml — about the OPERATOR, never about the system
operator:
actor: act-andrew # default for every --as cascade
servers: # operator-owned endpoint definitions
intel-dev:
url: http://127.0.0.1:8080
prod:
url: https://graph.modernrelay.ai
# No token here, ever. Resolution: §D4.
aliases: # personal shorthand over CLUSTER-owned queries
triage: # (the query is the shared contract; the alias,
server: intel-dev # its defaults, and its name are mine — RFC-008)
graph: spike
query: weekly_triage
defaults:
output: table # read --format default
```
Unknown keys are a **warning, not an error** in this layer (an operator file
written by a newer CLI must not brick an older one; contrast with
`cluster.yaml`, where unknown keys are deliberately fatal because they
change what a *plan* means).
### D3. Precedence and the merge rule
The end-state cascade is short, because the team surface (cluster config)
deliberately carries **no operator-resolvable keys** — no actor, no tokens,
no output preferences. Identity can never come from a checkout:
```
flag > env > operator config > built-in
```
During RFC-008's deprecation window, a legacy `omnigraph.yaml` slots in
between env and operator config (its keys win over operator defaults,
preserving today's behavior for unmigrated setups) — with the §D5
credential inversion: **credentials and endpoint definitions never come
from a legacy/checkout file when an operator-layer definition exists for
the same server name.**
Merging is **key-level**: scalars override per key; maps (`servers:`,
`aliases:`) merge per *entry*, and entries merge per *field* (finding #13
`merge_map` replacing whole entries silently dropped sibling fields).
Concretely for the two flows this slice touches:
- **Actor**: `--as` > legacy `cli.actor` (window only, unchanged semantics)
> `operator.actor` > none (commands that need an actor keep failing
loudly).
- **Output format**: `--format` > legacy default (window only) >
`defaults.output` > `table`.
### D4. Credentials: keyed by server name, by-reference always
Adopted from RFC-002 §5 unchanged, minus the keychain (a later source in
the same chain). For a server named `<name>`, the resolution chain is:
1. `OMNIGRAPH_TOKEN_<NAME>` (uppercased, `-``_`) — explicit env, wins.
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, anywhere (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: a file that arrives with a
*repo checkout* could redirect where requests go and what secrets they
carry. In the end state this is closed by construction — cluster config has
no server/credential keys at all, and the operator surface never comes from
a checkout. The rules below therefore govern the **RFC-008 window** (while
legacy `omnigraph.yaml` still loads) and stand as the permanent law for any
future checkout-supplied surface:
1. **A checkout-supplied file may *reference* a server by name; it may not
*redefine* an operator-defined server.** If a legacy `./omnigraph.yaml`
declares `servers.prod.url` and `~/.omnigraph/config.yaml` also defines
`prod`, the operator definition wins and the CLI warns about the
shadowed entry. A legacy-only server name keeps working (compat), but
the keyed-credentials chain (§D4 steps 12) 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 checkout-layer
env-files cannot *override* variables already set in the process or by
the operator layer** — first-set-wins, operator-before-checkout (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
`~/.omnigraph/config.yaml` (+ `OMNIGRAPH_HOME`, `~`-expansion, warn-only
unknown keys), `operator.actor` joining the `--as` cascade,
`defaults.output` joining the format cascade, `OMNIGRAPH_CONFIG` env for
the CLI's `--config`. Docs: `cli-reference.md` gains the two-surface
table.
2. **PR 2 — keyed credentials.** `servers:` in the operator layer, the
§D4 chain (env + credentials file), the §D5 trust rules, and
`omnigraph login <name>` (atomic write, `0600`). Legacy mechanisms
untouched and tested-as-untouched.
3. **PR 3 — operator targeting.** `--server <name>` on remote-capable
commands and `aliases:` in the operator layer (server + graph + query +
default params), resolving through operator-defined servers. This is
the *bridge* toward RFC-002's locator — multi-server addressing in a
safe, minimal form without the `GraphLocator` rework — and the
replacement RFC-008 needs before legacy aliases can migrate.
RFC-008's deprecation stages begin only after PRs 12 are on main: the
operator surface must exist before `config migrate` has somewhere to move
keys to.
## Open questions
- Should `operator.actor` apply to *local* (embedded-engine) writes too, or
only where a server/cluster boundary exists? Leaning yes-everywhere: one
identity chain (§D6 one-path rule), and local audit rows get better.
- Does `defaults.output` belong in slice 1, or is identity-only an even
cleaner first PR? (Cost of including it is one cascade hop; value is
immediate.)
- `omnigraph config view --resolved` (RFC-002 had it; #139 shipped a
version) — slice 1 or slice 2? It materially helps debugging precedence,
which argues early.
## Relationship to RFC-002 and RFC-008
**RFC-008 is the other half of this design**: this RFC builds the operator
surface; RFC-008 retires the mixed-ownership file
([rfc-008-deprecate-omnigraph-yaml.md](rfc-008-deprecate-omnigraph-yaml.md)),
leaving exactly two config surfaces — cluster (team) and operator (person).
Every mention of `omnigraph.yaml` in this RFC describes the deprecation
window only. Sequencing couples them: RFC-007 PRs 12 land first, then
RFC-008's migration stages run against them.
RFC-002 remains the umbrella architecture. This RFC implements its §2
(layered config, global-first), §4 (file naming / one dir), and §5
(credentials) in their minimal load-bearing form, and explicitly defers §1
(`GraphLocator`/targets), §3 (roles), and the State layer. If/when the
locator work resumes, it builds on these layers rather than re-landing
them. RFC-002's header should gain a pointer here once this merges.

View file

@ -0,0 +1,174 @@
# RFC: Deprecate `omnigraph.yaml` — One Concern per Config Surface
**Status:** Proposed
**Date:** 2026-06-11
**Builds on:** [rfc-007-operator-config.md](rfc-007-operator-config.md) (the
operator layer that absorbs the identity/credential keys),
[rfc-005-server-cluster-boot.md](rfc-005-server-cluster-boot.md) (Landed —
cluster-booted serving), RFC-006 storage roots (landed: #186/#190/#194).
**Supersedes in part:** RFC-007's "project layer" framing (§Relationship
below) and [rfc-002-config-cli-architecture.md](rfc-002-config-cli-architecture.md)'s
assumption that `omnigraph.yaml` remains the project manifest.
**Target release:** staged; final removal at the next major (see Sequencing).
## Summary
Retire `omnigraph.yaml`. It is three unrelated concerns wearing one
filename — server deployment config, project/CLI conveniences, and operator
identity — and the mixture is not a cosmetic wart but the root cause of a
recurring class of problems: operators keeping personal copies of "project"
files, repo checkouts able to carry credential-adjacent keys (the #139
security findings), `omnigraph init` scaffolding config into unrelated
directories, and every config discussion needing a paragraph to establish
which of the three files is meant.
The end state is **two config surfaces with single owners**:
| Surface | Owner | Declares |
|---|---|---|
| **Cluster config** (`cluster.yaml` + catalog) | the team, in a repo | what the system *is*: graphs, schemas, queries, policies, storage |
| **Operator config** (`~/.omnigraph/`) | one person, in `$HOME` | who *I* am: identity, credentials, known servers, ergonomics |
plus **flags/env** for the zero-config tier (one graph, one server, no
control plane) — which already works today with no file at all.
`omnigraph.yaml` has no role left once every key has a better home. This
RFC gives each key that home, and stages the retirement so that no working
setup breaks without a loud warning, a migration command, and a full
deprecation cycle first.
## Motivation
- **It breaks the ownership logic.** A config file must have one owner. A
file that carries `graphs:` (team-owned, reviewable) next to `cli.actor`
(one person's identity) and `auth.env_file` (credential loading) can be
neither safely committed nor sensibly personal. Every real deployment
this cycle tripped on it: per-operator copies in `~/exp/intel`,
graph-scoped alias URIs that only make sense per-person, the #139
findings where a checkout-supplied file could redirect tokens.
- **The cluster made it redundant.** Since RFC-005/006, a cluster
deployment serves from the applied catalog — `--cluster` mode does not
read `omnigraph.yaml` *at all*. Stored queries, policies, bindings, and
graph addressing all have authoritative homes. What remains in
`omnigraph.yaml` for cluster users is dead weight that can silently
disagree with what is actually serving.
- **Two declarative dialects is one too many.** `cluster.yaml` and
`omnigraph.yaml` both declare graphs/queries/policies with different
schemas, different validation strictness, and different lifecycle
guarantees. Maintaining, documenting, and testing both — and explaining
when each applies — is a permanent tax (the "programming integrated over
time" lens says: this forks on every config-surface change).
## Non-Goals
- **Breaking anyone now.** Every `omnigraph.yaml` that works today keeps
working through the entire deprecation window, with warnings.
- **Retiring the zero-config tier.** `omnigraph-server s3://bucket/g.omni
--bind …` plus env vars stays first-class forever — that tier needs *no*
file, which is the point.
- **Forcing the control plane on single-graph users.** The migration target
for a multi-graph yaml deployment is a *minimal* cluster (file-rooted,
no bucket required, `cluster.yaml` barely longer than the `graphs:` map
it replaces) — but a single graph never needs even that.
- **Touching `cluster.yaml`** — its schema and strictness are unchanged.
## Where every key goes (the complete migration map)
The full `OmnigraphConfig` surface (verified against
`crates/omnigraph-server/src/config.rs:182-207`):
| `omnigraph.yaml` key | Concern | New home |
|---|---|---|
| `graphs.<name>.uri` | what exists / where | `cluster.yaml` `graphs:` (storage-root-derived) — or a flag/env for the zero-config tier |
| `graphs.<name>.queries`, top-level `queries:` | what exists | cluster catalog (`.gq` discovery, RFC-004/#183) |
| `graphs.<name>.policy.file`, top-level `policy.file`, `server.policy.file` | what's enforced | `cluster.yaml` `policies:` + `applies_to` bindings |
| `server.bind` | deployment runtime | `--bind` / env (already authoritative; the key is a default) |
| `server.graph` | deployment runtime | `--target`-style flag / env in the zero-config tier; meaningless under cluster boot |
| `graphs.<name>.bearer_token_env`, `auth.env_file` | credentials | operator credentials chain (RFC-007 §D4) |
| `cli.actor` | identity | `operator.actor` (RFC-007 §D3) |
| `cli.output_format`, `cli.table_*` | personal ergonomics | `defaults:` in operator config (RFC-007 §D2) |
| `cli.graph`, `cli.branch` | personal targeting | operator config: named servers + a per-operator default target (RFC-007 PR 3) |
| `aliases.<name>` | personal ergonomics over shared queries | operator config `aliases:` — the *queries* they invoke are cluster-owned; the *shorthand* is personal |
| `query.roots` | discovery convenience | obsolete — cluster query discovery (#183) replaced it |
| `project.name` | label | dropped (the cluster's `metadata.name` is the deployment label) |
Two placements worth defending:
- **Aliases are operator config, not cluster config.** The stored query is
the shared contract (catalog-owned, digest-pinned); an alias is one
person's shorthand with their favorite default params and target. Putting
aliases in the cluster would force team review on personal ergonomics;
leaving them per-directory recreates today's problem. Per-operator,
keyed by server/graph name, is the AWS-profile shape.
- **Multi-graph serving without a control plane migrates to a minimal
cluster, not to a new file.** The honest cost: `cluster import` + `apply`
once, on a `file://` root next to the graphs. The honest benefit: one
declarative dialect, one validation path, one serving source — and the
upgrade path to buckets/approvals is a one-line `storage:` change instead
of a re-platform.
## Deprecation mechanics
Per Hyrum's Law (the repo's own deny-list: shipped observable behavior is
contract), retirement is staged, loud, and tooled:
1. **Warn.** Loading `omnigraph.yaml` emits a one-line deprecation notice
naming the replacement for each key actually present in the file (not a
generic banner — the migration map above, applied to *your* file).
Suppressible per-process (`OMNIGRAPH_SUPPRESS_YAML_DEPRECATION=1`) for
CI logs during the window.
2. **Migrate.** `omnigraph config migrate` reads an existing
`omnigraph.yaml` and writes the split: the team half as a ready-to-review
`cluster.yaml` (+ moves query/policy files into the checkout layout),
the personal half merged into `~/.omnigraph/config.yaml` — printing a
diff-style summary and touching nothing without `--write`. The command
is the test of the migration map's completeness: any key it cannot
place is a bug in this RFC.
3. **Stop scaffolding.** `omnigraph init` stops generating
`omnigraph.yaml` (it currently scaffolds one into cwd — the source of
the test-pollution bug). `omnigraph cluster init` (new, small) scaffolds
a minimal `cluster.yaml` instead.
4. **Opt-in strict.** `OMNIGRAPH_NO_LEGACY_CONFIG=1` turns the warning into
an error — for teams that finished migrating and want regressions caught.
5. **Remove at the next major.** Loading the file becomes an error pointing
at `config migrate`. The `OmnigraphConfig` code path, the dual
query-registry loaders, and the yaml-mode server boot source are deleted
— the payoff that makes the whole exercise worth it.
Stages 13 can land in one release once RFC-007 PRs 12 exist (the operator
layer must exist before anything can migrate *to* it). Stage 4 the release
after. Stage 5 at the major, with the removal listed in release notes from
stage 1 onward.
## What this deletes, eventually
- The `OmnigraphConfig` struct and its 12-key surface, the
`load_config`/`load_cli_config` pair and its env-side-effect, the
scaffolder, and the legacy resolution paths (`resolve_cli_graph`'s dual
modes — finding #11's root cause).
- The yaml-mode multi-graph server boot (`ServerConfigMode::Multi` keeps
existing — cluster boot constructs it — but its `omnigraph.yaml` source
goes).
- An entire class of documentation ("which file does X go in?") and the
#139 security surface (a checkout cannot hijack what no longer loads).
## Relationship to RFC-007 and RFC-002
RFC-007 ships the operator layer this RFC migrates *to*; its "project
layer" language should be read as transitional — after this RFC, the
project layer **is** the cluster checkout, and RFC-007's PR 3 (project
`server:` references) applies to `cluster.yaml`-adjacent operator targeting
rather than to `omnigraph.yaml`. RFC-002's locator/state-layer work, if
resumed, targets the two-surface world directly. RFC-002's file-naming
decisions (`~/.omnigraph/` as the one dir) are unaffected.
## Open questions
- **Window length**: one minor release between warn (stage 1) and strict
(stage 4), or two? Cookbooks, skills, and the deployment docs all need
the same pass; the migration command makes a short window defensible.
- **`omnigraph login` vs `config migrate` ordering** — both write
`~/.omnigraph/`; whichever lands first establishes the file-locking and
atomic-write helpers the other reuses.
- **Does the MCP server config** (RFC-003) reference `omnigraph.yaml`
anywhere that needs the same treatment? To be audited in stage 1.