Skip to main content

Scope Merging

The loader can merge up to four config scopes into one effective config.

Scope discovery locations

Unless you override paths programmatically, pkg/config.DiscoverScopePaths looks here:

ScopeDefault path
Managed/etc/oas-cli/.cli.json
User$XDG_CONFIG_HOME/oas-cli/.cli.json or ~/.config/oas-cli/.cli.json
Project<working-dir>/.cli.json
Local<working-dir>/.cli.local.json

oclird and the runtime API typically load a project config by explicit path, but the same merge rules still apply inside the config package.

Load order

Scopes merge in this order:

  1. managed
  2. user
  3. project
  4. local

Later scopes override or extend earlier scopes depending on the field type.

Validation model

Each scope file is validated with a relaxed schema first:

  • required fields are not enforced per-scope
  • minProperties checks are relaxed per-scope

Then the final merged config is validated strictly.

That means a local override file can safely contain only the keys it wants to change.

Exact merge rules

FieldMerge behavior
clilast non-empty value wins
mode.defaultlast non-empty value wins
sourcesmerged per source ID; individual fields override prior values
sources.*.refreshreplaced as a whole when present in a later scope
servicesmerged per service ID
services.*.source, services.*.aliaslast non-empty value wins
services.*.overlays, skills, workflowswhole list is replaced when present in a later scope
curation.toolSets.*.allow, .denyappended with de-duplication
agents.defaultProfilelast non-empty value wins
agents.profiles.*merged per profile field
policy.deny, policy.approvalRequiredappended with de-duplication
policy.allowExecSecretslast explicit boolean wins
secretsreplaced per secret ID

enabled defaulting for sources

A new source with no explicit enabled field is treated as enabled. Later scopes can then flip it on or off.

Example pattern:

  • managed scope defines a source
  • user scope sets enabled: false
  • local scope sets enabled: true

That exact flow is covered by repository tests.

Managed policy nuance

When the managed scope contributes policy.deny, the loader copies those patterns into an internal ManagedDeny list in addition to the public policy.deny list.

That matters because the current policy engine enforces ManagedDeny directly at execution time.

Base directory selection

Relative document references are resolved from the effective base directory, chosen in this order:

  1. the directory containing .cli.local.json
  2. the directory containing .cli.json
  3. the working directory passed into the loader

Practical layout recommendation

A good split is:

  • managed: organization-wide hard restrictions
  • user: personal defaults or source toggles
  • project: shared service definitions, overlays, skills, workflows
  • local: uncommitted secrets paths, source enable/disable flips, or local overrides

See Modes and profiles for the runtime behavior that sits on top of the merged config.