Skip to content

Configurator

These modules cover typed oa-configurator 1.x inspection and the provider-neutral write flow. Start with the configuration guide for the analyst experience, provider responsibilities, workflow composition, and secret boundary.

Read-only adapter over the public shape of oa-configurator stack models.

OAConfiguratorAdapter

Build safe, read-only views from an oa-configurator 1.x stack.

Core sections are read from the public StackConfig and concrete pydantic models. [tools.*] sections are typed from the omop.config entry-point registry, so a package that registers a PackageConfigBase gets the same sensitivity and reference inspection without the caller doing anything. A caller may still pass resolved instances, which win over the registry. Editable candidates and persistence remain outside groundskeeping.

A section with no usable schema is rendered by shape only -- see :meth:_untyped_entry for why that is the safe reading rather than the cautious one.

as_tree_view

as_tree_view(snapshot: ConfiguratorSnapshot) -> TreeView

snapshot

snapshot(
    stack_config: StackConfig,
    *,
    config_path: str | Path | None = None,
    package_configs: Iterable[PackageConfigBase] = (),
    title: str = "Stack configuration",
) -> ConfiguratorSnapshot

Textual-free models for presenting stack configuration.

ConfigReferenceStatus

Bases: StrEnum

Whether a named configuration reference resolves in the inspected stack.

MISSING class-attribute instance-attribute

MISSING = 'missing'

RESOLVED class-attribute instance-attribute

RESOLVED = 'resolved'

WRONG_KIND class-attribute instance-attribute

WRONG_KIND = 'wrong kind'

ConfigReferenceView dataclass

ConfigReferenceView(
    section: ConfigTargetKind,
    name: str,
    status: ConfigReferenceStatus,
    expected_type: str,
    actual_type: str | None = None,
)

Presentation-safe description of one RefTo field.

actual_type class-attribute instance-attribute

actual_type: str | None = None

expected_type instance-attribute

expected_type: str

name instance-attribute

name: str

section instance-attribute

section: ConfigTargetKind

status instance-attribute

status: ConfigReferenceStatus

__str__

__str__() -> str

ConfigSectionView dataclass

ConfigSectionView(
    target: ConfigTarget,
    fields: Mapping[str, object] = dict(),
    children: tuple[ConfigSectionView, ...] = (),
    notes: tuple[str, ...] = (),
)

Read-only view of one configuration section.

children class-attribute instance-attribute

children: tuple[ConfigSectionView, ...] = ()

fields class-attribute instance-attribute

fields: Mapping[str, object] = field(default_factory=dict)

notes class-attribute instance-attribute

notes: tuple[str, ...] = ()

target instance-attribute

target: ConfigTarget

ConfigTarget dataclass

ConfigTarget(
    kind: ConfigTargetKind,
    key: str,
    title: str,
    status: SemanticStatus = SemanticStatus.INFO,
)

One selectable configuration target.

key instance-attribute

key: str

kind instance-attribute

kind: ConfigTargetKind

status class-attribute instance-attribute

status: SemanticStatus = SemanticStatus.INFO

title instance-attribute

title: str

ConfigTargetKind

Bases: StrEnum

Stable kinds a configuration provider may expose for inspection.

CONNECTION class-attribute instance-attribute

CONNECTION = 'connection'

DATABASE class-attribute instance-attribute

DATABASE = 'database'

LOGGING class-attribute instance-attribute

LOGGING = 'logging'

MODEL class-attribute instance-attribute

MODEL = 'model'

PROVIDER class-attribute instance-attribute

PROVIDER = 'provider'

TOOL class-attribute instance-attribute

TOOL = 'tool'

VECTOR_STORE class-attribute instance-attribute

VECTOR_STORE = 'vector_store'

ConfiguratorSnapshot dataclass

ConfiguratorSnapshot(
    title: str,
    path: str | None,
    sections: tuple[ConfigSectionView, ...],
)

Read-only snapshot of an effective oa-configurator stack.

path records where the inspected configuration came from. It is display metadata only and must not be interpreted as an instruction to write there.

path instance-attribute

path: str | None

sections instance-attribute

sections: tuple[ConfigSectionView, ...]

title instance-attribute

title: str

RedactedValue dataclass

RedactedValue(label: str = '<redacted>')

Marker used when a known secret must not enter ordinary view models.

label class-attribute instance-attribute

label: str = '<redacted>'

__str__

__str__() -> str

Provider-neutral contracts for safe configuration write flows.

Groundskeeping owns the operator sequence, not the configuration candidate. A mutation service keeps real candidate state behind an opaque session token and returns only presentation-safe plans and results. Submitted values are method arguments rather than dataclass fields so they cannot accidentally settle in snapshots, reprs, or widget state.

ConfigApplyIntent dataclass

ConfigApplyIntent(
    target: ConfigTarget,
    operation: MutationOperation,
    apply_token: str,
    expected_revision: str | None,
)

Opaque, revision-aware request to apply a previously prepared plan.

apply_token instance-attribute

apply_token: str

expected_revision instance-attribute

expected_revision: str | None

operation instance-attribute

operation: MutationOperation

target instance-attribute

target: ConfigTarget

ConfigApplyResult dataclass

ConfigApplyResult(
    status: ConfigApplyStatus,
    summary: str,
    detail: str | None = None,
    refresh_pages: frozenset[str] = frozenset(),
)

Portable outcome returned by a mutation provider.

summary and detail are rendered to the operator and written to application logs, so both must be presentation-safe: no submitted values, no secrets, no provider tracebacks, no absolute paths a host would not otherwise disclose.

summary says what happened in one line. detail carries the operator's next action and should be present whenever the status is not APPLIED — "Reload the configuration and review the change again" for a conflict, "Grant write access to the configuration directory and retry" for a failure. Omit it when there is nothing useful to add; do not restate summary.

refresh_pages names the host page keys whose data this change invalidated.

detail class-attribute instance-attribute

detail: str | None = None

refresh_pages class-attribute instance-attribute

refresh_pages: frozenset[str] = frozenset()

status instance-attribute

status: ConfigApplyStatus

summary instance-attribute

summary: str

ConfigApplyStatus

Bases: StrEnum

How one apply attempt ended.

A host renders different remediation for each member, so a provider must classify its errors rather than reach for the nearest label. Two questions separate the four: was anything written, and if not, was the request itself at fault?

Status Meaning Typical cause
APPLIED The change was persisted.
CONFLICTED The stored configuration changed after the plan was prepared; the expected revision no longer matches. Nothing was written. Another writer saved between plan and apply.
REJECTED The request itself was not acceptable, and no write was attempted. Consumed or unknown apply token, intent not matching the prepared plan, candidate failing validation, ownership or policy forbidding the write.
FAILED The write was attempted and errored. The previous configuration remains authoritative. Filesystem permissions, disk full, I/O error, serialisation failure.

APPLIED class-attribute instance-attribute

APPLIED = 'applied'

The change was persisted; the stored configuration now reflects it.

CONFLICTED class-attribute instance-attribute

CONFLICTED = 'conflicted'

The stored revision moved after planning. Nothing was written.

FAILED class-attribute instance-attribute

FAILED = 'failed'

The write was attempted and errored; the previous configuration stands.

REJECTED class-attribute instance-attribute

REJECTED = 'rejected'

The request was not acceptable, and no write was attempted.

ConfigDiff dataclass

ConfigDiff(
    target: ConfigTarget,
    entries: tuple[ConfigDiffEntry, ...],
)

A redacted structural diff suitable for review UI.

changed property

changed: bool

entries instance-attribute

entries: tuple[ConfigDiffEntry, ...]

target instance-attribute

target: ConfigTarget

ConfigDiffEntry dataclass

ConfigDiffEntry(
    field: str,
    before: object,
    after: object,
    sensitive: bool = False,
)

One presentation-safe change in a configuration plan.

after instance-attribute

after: object

before instance-attribute

before: object

field instance-attribute

field: str

sensitive class-attribute instance-attribute

sensitive: bool = False

__post_init__

__post_init__() -> None

__repr__

__repr__() -> str

ConfigDraft dataclass

ConfigDraft(
    target: ConfigTarget,
    operation: MutationOperation,
    session_token: str,
    changed_fields: frozenset[str] = frozenset(),
    expected_revision: str | None = None,
)

Safe identity and progress for provider-owned candidate state.

changed property

changed: bool

changed_fields class-attribute instance-attribute

changed_fields: frozenset[str] = frozenset()

expected_revision class-attribute instance-attribute

expected_revision: str | None = None

operation instance-attribute

operation: MutationOperation

session_token instance-attribute

session_token: str

target instance-attribute

target: ConfigTarget

ConfigMutationService

Bases: Protocol

Provider boundary for configuration candidate state and persistence.

Implementations must consume an apply token at the start of every apply attempt, including conflict, rejection, and failure. submit may inspect real values only for the duration of the call; the service owns any candidate state retained behind draft.session_token. Returned objects must be safe to render and log.

Every method may raise :class:UnavailableMutationService when the provider as a whole cannot serve requests. begin() additionally raises :class:MutationOperationUnsupported to refuse one operation. Any other exception is treated as a provider defect: the controller logs it without its message and shows the operator a generic failure, so a condition a host should act on must use one of the two typed exceptions.

apply

apply(intent: ConfigApplyIntent) -> ConfigApplyResult

begin

begin(
    target: ConfigTarget, operation: MutationOperation
) -> ConfigDraft

Open a provider-owned candidate session and capture its base revision.

Raises:

Type Description
MutationOperationUnsupported

The operation is not available for this target — already exists, does not exist, read-only, or forbidden by policy. The message is shown to the operator.

UnavailableMutationService

The provider cannot serve requests at all.

cancel

cancel(draft: ConfigDraft) -> None

capabilities

capabilities(
    target: ConfigTarget, operation: MutationOperation
) -> MutationCapabilities

Report whether one operation is available for one target.

This is the non-raising way to ask what begin() would do. It still raises :class:UnavailableMutationService when the provider cannot answer at all.

fields

fields(draft: ConfigDraft) -> tuple[FieldSpec, ...]

plan

plan(draft: ConfigDraft) -> ConfigPlan

submit

submit(
    draft: ConfigDraft,
    step_key: str,
    values: Mapping[str, object],
    *,
    discard_fields: frozenset[str] = frozenset(),
) -> ConfigStepResult

ConfigPlan dataclass

ConfigPlan(
    target: ConfigTarget,
    operation: MutationOperation,
    diff: ConfigDiff,
    effects: tuple[EffectRef, ...] = (),
    issues: tuple[ValidationIssue, ...] = (),
    warnings: tuple[str, ...] = (),
    apply_token: str | None = None,
    expected_revision: str | None = None,
)

Provider-produced, presentation-safe plan for an apply attempt.

apply_token class-attribute instance-attribute

apply_token: str | None = None

diff instance-attribute

diff: ConfigDiff

effects class-attribute instance-attribute

effects: tuple[EffectRef, ...] = ()

expected_revision class-attribute instance-attribute

expected_revision: str | None = None

issues class-attribute instance-attribute

issues: tuple[ValidationIssue, ...] = ()

operation instance-attribute

operation: MutationOperation

ready property

ready: bool

target instance-attribute

target: ConfigTarget

warnings class-attribute instance-attribute

warnings: tuple[str, ...] = ()

ConfigStepResult dataclass

ConfigStepResult(
    issues: tuple[ValidationIssue, ...] = (),
    changed_fields: frozenset[str] = frozenset(),
    future_fields: tuple[FieldSpec, ...] = (),
)

Presentation-safe outcome of staging one wizard step.

changed_fields is the complete current set for the provider session, not only the fields submitted in this call. This lets a provider account for defaults, unchanged updates, and branch invalidation without exposing candidate values.

future_fields may replace presentation descriptors for later, uncompleted workflow fields after an accepted step. The controller rejects callbacks, changes to completed fields, and changes that weaken a field's kind or sensitivity.

accepted property

accepted: bool

changed_fields class-attribute instance-attribute

changed_fields: frozenset[str] = frozenset()

future_fields class-attribute instance-attribute

future_fields: tuple[FieldSpec, ...] = ()

issues class-attribute instance-attribute

issues: tuple[ValidationIssue, ...] = ()

EffectRef dataclass

EffectRef(
    impact_kind: str,
    source_target: ConfigTarget,
    label: str,
    destination_target: ConfigTarget | None = None,
    field_key: str | None = None,
    status: SemanticStatus = SemanticStatus.INFO,
)

A structural, presentation-safe impact between configuration targets.

destination_target class-attribute instance-attribute

destination_target: ConfigTarget | None = None

field_key class-attribute instance-attribute

field_key: str | None = None

impact_kind instance-attribute

impact_kind: str

label instance-attribute

label: str

source_target instance-attribute

source_target: ConfigTarget

status class-attribute instance-attribute

status: SemanticStatus = SemanticStatus.INFO

__str__

__str__() -> str

MutationCapabilities dataclass

MutationCapabilities(
    target: ConfigTarget,
    operation: MutationOperation,
    supported: bool,
    reason: str | None = None,
)

Whether one operation is available for one target.

operation instance-attribute

operation: MutationOperation

reason class-attribute instance-attribute

reason: str | None = None

supported instance-attribute

supported: bool

target instance-attribute

target: ConfigTarget

MutationOperation

Bases: StrEnum

A portable configuration operation.

CREATE class-attribute instance-attribute

CREATE = 'create'

UPDATE class-attribute instance-attribute

UPDATE = 'update'

MutationOperationUnsupported

Bases: ValueError

This provider will not begin this operation on this target.

Raise this from begin() when the operation is legitimately unavailable — the entry already exists and the provider only creates, the entry does not exist and the provider only updates, the configuration is read-only, or policy forbids the change. The message is shown to the operator, so it must be presentation-safe and say why.

It subclasses ValueError so hosts that already catch ValueError around begin() keep working, but a typed refusal lets a host separate "not available right now" from a programming error it should surface as a bug.

capabilities() answers the same question without raising, and a host that calls it first will normally never see this. Providers should raise it anyway: a capability answer can go stale between the check and the call.

UnavailableMutationService

Bases: RuntimeError

The provider exists but cannot currently serve mutation requests.

Raise this when the whole service is out of action — an unreadable or malformed configuration file, a backing store that cannot be reached. It is not the way to refuse one operation; see :class:MutationOperationUnsupported.

build_config_diff

build_config_diff(
    target: ConfigTarget,
    original_fields: Mapping[str, object],
    candidate_fields: Mapping[str, object],
    *,
    sensitive_fields: frozenset[str] = frozenset(),
) -> ConfigDiff

Build a diff after replacing every declared sensitive value.

Both mappings must come from the same projection of the same configuration shape. A field absent from one side and present on the other is reported as a change, so projecting the two sides differently produces a diff that is wrong in both directions.

The usual way to get this wrong is to normalise only one side — flattening the stored base with something like exclude_none=True while the candidate comes back from a validator with every default materialised. Every defaulted field then appears as None -> <default>. Flatten both sides through the same call, with the same options, before calling this function.

sensitive_fields is the set of field keys whose values must never be rendered; both sides of such an entry are replaced with :class:RedactedValue, which also means a sensitive field that changed is still reported as a change.

resolve_operation

resolve_operation(
    service: ConfigMutationService, target: ConfigTarget
) -> MutationOperation

Return the operation a host should use for target: update it, or create it.

A :class:~groundskeeping.configurator.controller.ConfigWorkflowSpec is built for one fixed operation, so a host that offers a single "Configure" action must decide which one before constructing the controller. This encodes that decision once: UPDATE when the provider supports updating this target, CREATE otherwise.

operation = resolve_operation(service, target)
controller = ConfigWizardController(workflow(operation), service)

Checking UPDATE first is deliberate. A provider that supports both reports both as supported, and updating an entry the operator already has is the safer default.

A host that wants create-only or update-only behaviour should keep passing an explicit operation instead of calling this — the controller then blocks with the provider's own reason when that operation is unsupported, which is the correct outcome for a "Create database" action aimed at a database that already exists.

Raises:

Type Description
UnavailableMutationService

The provider cannot answer capability questions.

Generic configuration wizard built over :mod:.mutation contracts.

The workflow is declarative: applications group provider field keys into ordered steps and attach simple equality conditions for branches. Groundskeeping owns safe navigation state and branch recalculation. Providers own validation, candidate values, planning, and apply semantics. There is intentionally no callback-shaped branching API and no provider candidate object crosses this module.

ConfigBranchCondition dataclass

ConfigBranchCondition(
    field_key: str,
    values: frozenset[object],
    negated: bool = False,
)

Show a step when an earlier field is in, or outside, a set of values.

field_key instance-attribute

field_key: str

negated class-attribute instance-attribute

negated: bool = False

values instance-attribute

values: frozenset[object]

matches

matches(value: object) -> bool

ConfigWizardController

ConfigWizardController(
    workflow: ConfigWorkflowSpec,
    service: ConfigMutationService,
)

Drive one provider-owned mutation session through the generic wizard UI.

service instance-attribute

service = service

spec instance-attribute

spec = WizardSpec(
    key=workflow.key,
    title=workflow.title,
    purpose=workflow.purpose,
    apply_label=workflow.apply_label,
)

workflow instance-attribute

workflow = workflow

apply

apply() -> WizardResult

back

back() -> WizardSnapshot

cancel

cancel() -> WizardResult

review

review() -> WizardTransition

start

start() -> WizardSnapshot

submit

submit(values: Mapping[str, object]) -> WizardTransition

ConfigWorkflowSpec dataclass

ConfigWorkflowSpec(
    key: str,
    target: ConfigTarget,
    operation: MutationOperation,
    title: str,
    purpose: str,
    steps: tuple[ConfigWorkflowStep, ...],
    apply_label: str = "Apply",
)

Application-owned copy and branching for a generic write flow.

apply_label class-attribute instance-attribute

apply_label: str = 'Apply'

key instance-attribute

key: str

operation instance-attribute

operation: MutationOperation

purpose instance-attribute

purpose: str

steps instance-attribute

steps: tuple[ConfigWorkflowStep, ...]

target instance-attribute

target: ConfigTarget

title instance-attribute

title: str

ConfigWorkflowStep dataclass

ConfigWorkflowStep(
    key: str,
    title: str,
    field_keys: tuple[str, ...],
    purpose: str | None = None,
    kind: ConfigWorkflowStepKind = ConfigWorkflowStepKind.FORM,
    when: tuple[ConfigBranchCondition, ...] = (),
)

Declarative grouping of provider fields into one wizard step.

field_keys instance-attribute

field_keys: tuple[str, ...]

key instance-attribute

key: str

kind class-attribute instance-attribute

kind: ConfigWorkflowStepKind = ConfigWorkflowStepKind.FORM

purpose class-attribute instance-attribute

purpose: str | None = None

title instance-attribute

title: str

when class-attribute instance-attribute

when: tuple[ConfigBranchCondition, ...] = ()

ConfigWorkflowStepKind

Bases: StrEnum

CHOICE class-attribute instance-attribute

CHOICE = 'choice'

FORM class-attribute instance-attribute

FORM = 'form'

Reusable lifecycle assertions for external configuration providers.

InvalidSubmissionHook module-attribute

InvalidSubmissionHook = Callable[
    [ConfigMutationService, ConfigDraft], ConfigStepResult
]

MutationServiceFactory module-attribute

MutationServiceFactory = Callable[[], ConfigMutationService]

MutationServiceHook module-attribute

MutationServiceHook = Callable[
    [ConfigMutationService], None
]

MutationConformanceError

Bases: AssertionError

A provider did not honour an observable mutation-service guarantee.

MutationConformanceHooks dataclass

MutationConformanceHooks(
    invalid_submission: InvalidSubmissionHook | None = None,
    expected_invalid_fields: frozenset[
        str | None
    ] = frozenset(),
    advance_revision: MutationServiceHook | None = None,
    prepare_warning: MutationServiceHook | None = None,
    prepare_plan_error: MutationServiceHook | None = None,
    prepare_rejection: MutationServiceHook | None = None,
    prepare_failure: MutationServiceHook | None = None,
    make_unavailable: MutationServiceHook | None = None,
    prepare_unsupported: MutationServiceHook | None = None,
    unsupported_operation: MutationOperation | None = None,
)

Provider-specific fault injection for the portable lifecycle suite.

Hooks receive a fresh service instance. They should alter external state or a test double's next outcome without returning candidate values. A production provider test commonly uses advance_revision to perform an out-of-band write after planning.

advance_revision class-attribute instance-attribute

advance_revision: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

expected_invalid_fields class-attribute instance-attribute

expected_invalid_fields: frozenset[str | None] = frozenset()

invalid_submission class-attribute instance-attribute

invalid_submission: InvalidSubmissionHook | None = field(
    default=None, repr=False, compare=False
)

make_unavailable class-attribute instance-attribute

make_unavailable: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

prepare_failure class-attribute instance-attribute

prepare_failure: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

prepare_plan_error class-attribute instance-attribute

prepare_plan_error: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

prepare_rejection class-attribute instance-attribute

prepare_rejection: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

prepare_unsupported class-attribute instance-attribute

prepare_unsupported: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

prepare_warning class-attribute instance-attribute

prepare_warning: MutationServiceHook | None = field(
    default=None, repr=False, compare=False
)

unsupported_operation class-attribute instance-attribute

unsupported_operation: MutationOperation | None = None

assert_mutation_service_conformance

assert_mutation_service_conformance(
    service_factory: MutationServiceFactory,
    target: ConfigTarget,
    operation: MutationOperation,
    submissions: Sequence[tuple[str, Mapping[str, object]]],
    *,
    hooks: MutationConformanceHooks | None = None,
    secret_canary: str | None = None,
) -> None

Exercise supported lifecycle behavior against isolated provider instances.

The core assertions cover capability discovery, begin/fields/stage/plan/apply, single-use tokens, diff projection symmetry, and cancellation. Supplying hooks additionally proves validation, warning/non-ready planning, stale revision conflict, rejection, operational failure, unavailability, and unsupported-operation behavior. Values remain transient method arguments and are never included in conformance errors.

submissions must reach a valid candidate; they are staged twice against the same provider instance so the suite can require that restaging applied values reports no change. A provider that stops supporting operation once the entry exists — a create-only provider, most commonly — is not asked that question, so run the suite for UPDATE as well as CREATE when the provider supports both.