Skip to content

architecture: rethink config loading system and config files #774

Open

Description

@jbeda

Problem

Mecatl's configuration system has grown from several legitimate needs, but its current ownership model is difficult to reason about:

  • command roots parse flags and some environment/auth-file inputs;
  • app.Build constructs and reads/resolves operator settings through permconfig;
  • per-workspace, trust-gated project configuration must resolve after a session workspace is known;
  • provider and MCP credentials have different loading/lifecycle seams;
  • some settings are load-time snapshots while others are re-resolved or refreshed.

This makes it unclear which settings are process-static, which can safely become live-reloadable, and where a Kubernetes/database-backed configuration source should attach.

Goal

Design a coherent configuration architecture that separates:

  1. source acquisition — CLI flags, environment, operator files, auth files, Kubernetes ConfigMaps/Secrets, database/remote sources;
  2. process-wide operator resolution — typed validation and explicit per-field precedence/merge policy;
  3. workspace/session resolution — trust-gated project settings that must remain dependent on a workspace root;
  4. runtime composition — app.Build consumes resolved configuration and injected resolvers/loaders rather than owning file-source selection.

The design must classify each configuration subtree as one of:

  • process-static / restart required;
  • reloadable atomically at runtime;
  • per-session/per-workspace resolved;
  • credential lifecycle-managed.

It should define safe live-reload semantics: validation before swap, last-known-good behavior, diagnostics, atomicity, which resources are rebuilt or retained, and security/trust constraints. It must preserve existing non-generic merge policies such as deny dominance, operator-only settings, CLI precedence, secret separation, and provider endpoint precedence.

Non-goals

  • Replacing the system opportunistically inside the operator-defined-provider PR.
  • A generic YAML overlay that erases security/trust-specific merge rules.
  • Making secrets available in ordinary settings.yaml.
  • Claiming every setting can be live reloaded.

Starting evidence

  • internal/adapter/permconfig combines user-global/operator and per-workspace project configuration.
  • internal/app.Build constructs the resolver and folds several operator subtrees.
  • internal/cliconfig resolves command-side flags, environment, auth-file snapshots, and profile loaders.
  • Provider profile loading now has an injected composition seam, while MCP already has a profile-loader/lifecycle seam.
  • docs/configuration-reference.md documents schema keys, but not a cross-root effective-configuration or reloadability model.

Related

Activity

  1. jbeda commented on Aug 26, 2026

    @jbeda
    ContributorAuthor

    Concrete symptom: composition downcasts a policy resolver for operator settings

    internal/app.Config stores permResolver as the intentionally narrow permpolicy.RuleResolver, because the permission-policy consumer should only resolve workspace rules. app.Build then repeatedly downcasts it back to *permconfig.Resolver to access unrelated operator settings, for example provider definitions/overrides at internal/app/build.go:1431.

    The assertion is guarded and works in production because Build constructs that concrete resolver, but it is a sign that one object is serving two contracts:

    • a narrow per-workspace permission-rule resolver consumed by policy; and
    • a broad operator-settings accessor consumed by composition for providers, MCP, models, guardrails, posture, retention, and similar subtrees.

    Do not widen permpolicy.RuleResolver with unrelated configuration accessors. The redesign should provide separate views over one underlying concrete source:

    *permconfig.Resolver
      ├─ permpolicy.RuleResolver  → policy/workspace rules
      └─ OperatorConfigSource     → composition/operator settings
    

    An interim implementation could retain a concrete operator-resolver reference inside internal/app; the longer-term source abstraction should permit file, Kubernetes, database, or remote operator configuration without app.Build downcasts.

  2. jbeda commented on Oct 28, 2026

    @jbeda
    ContributorAuthor

    Additional configuration/composition cleanup evidence

    Reviewing MCP credential resolution in PR #2583 exposed another instance of the ownership problem tracked here:

    • app.Config.permResolver stores the settings resolver as the narrow permpolicy.RuleResolver interface, even though composition needs the wider operator-settings object.
    • buildPermResolver constructs a concrete *permconfig.Resolver, narrows it on return, and composition then repeatedly asserts it back to that concrete type to access provider definitions, credential-store configuration, MCP configuration, model routing, learning, retention, harness context, and other operator settings.
    • At the time of review, there were roughly three dozen such assertions across about 15 files under internal/app. A representative example is internal/app/build.go:1855 at PR head 538bfa59c54bedf069a0efbbb8150678574797f9.

    Suggested cleanup within this architecture work

    Retain the wider concrete settings resolver at the composition layer, preferably with a name such as settingsResolver, and narrow it to permpolicy.RuleResolver only at permission-policy and child-resolver boundaries. Do not widen the permission interface with unrelated operator-settings methods, and do not introduce another resolver/cache or settings-discovery pass.

    Preserve these constraints:

    • Explicitly handle nil before conversion to an interface, avoiding typed-nil permission resolvers.
    • Preserve settings construction order, project-trust admission, source precedence, and workspace/session pinning.
    • Keep narrow permission test seams; tests that currently install a fake rule resolver into the broad composition field should instead exercise the appropriate permission-wrapper boundary.

    This is follow-up work for #774, not scope to fold into the MCP credential PR. The current MCP implementation does not depend on this cleanup for correctness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions