31 KiB
ConfigKit Adoption
Purpose
Replace Notarius's application-owned YAML parsing, presence bookkeeping, default application, and configuration-inspection machinery with ConfigKit, while preserving Notarius ownership of pipeline semantics, module catalogs, runtime resolution, and invocation behavior.
This roadmap is based on ConfigKit v0.2.0, the current stable release in the
local ../ConfigKit checkout. ConfigKit's maintained consumer guide includes a
Notarius-shaped example, and its complete test suite passes locally. The
dependency starts at v0.2.0; completing source-aware normalization requires a
reviewed release with the extension below. Do not follow an unreviewed branch
tip. Ordered implementation work belongs in
the implementation plan.
Source Metadata Dependency
Review for implementation identified an API gap in v0.2.0: Resolution
exposes input presence and change recording, but not declaration origins or
source paths. Those are available only on the completed Result, whose
effective and explanation snapshots are already frozen. The target of
recording declaration-relative path normalization in those snapshots therefore
requires an upstream extension. The selected approach is to request source
origins and canonical source records during resolution, then adopt a reviewed
release providing that API. The implementation plan records the dependency
gate. Resolving paths only after loading with a separate inspection report was
considered but not selected; the agreed effective/explanation contract remains
the target. ConfigKit v0.2.0 alone is insufficient for that part of the target.
Motivation
The current configuration boundary under internal/core/config works, but it
reimplements a substantial general-purpose configuration system. In
particular, Notarius currently owns:
- strict YAML setup, duplicate-key and trailing-document checks;
- custom YAML decoders for module bindings, references, pipelines, steps, and lanes;
- pointer and private-flag bookkeeping to distinguish omitted values from explicit zero, false, empty, or null values;
- a large file-to-runtime application function that combines normalization, validation, conversion, and default derivation;
- operational environment overlays and their separate validation path; and
- bespoke cloning and redaction for effective configuration summaries.
ConfigKit already provides strict single-document YAML parsing, typed decode, input presence, typed defaults, recorded normalization and derived defaults, semantic diagnostics with source locations, detached results, redacted effective rendering, and value explanation. Adopting those facilities should reduce application-specific code and make configuration failures easier for an operator to understand without moving domain policy into the dependency.
This is a configuration-contract migration, not a mechanical parser swap.
ConfigKit deliberately rejects several conveniences supported by version 4,
and its dynamic integer representation differs from gopkg.in/yaml.v3.
Target End State
After this work:
- every command uses one ConfigKit-backed loading path;
- Notarius configuration files declare schema version 5 using the existing integer YAML convention;
- the file schema uses canonical object forms rather than YAML type-dependent shorthand;
- operators can import whole pipeline definitions and select a configuration profile for development, testing, or deployment;
- defaults, surviving-input presence, normalization, derived values, and
configuration-only validation are expressed through one ConfigKit
Definition; - ConfigKit diagnostics are rendered safely with semantic paths and available source locations at the CLI boundary;
- the successful load result retains ConfigKit metadata for safe effective configuration rendering and explanation;
- dynamic module-option integers have one documented
int64configuration representation and are range-checked when converted to runtimeintvalues; - pipeline and module resolution remains in Notarius and still fails before module preparation when a configured composition is invalid;
- configuration discovery and invocation-only CLI state remain application responsibilities;
NOTARIUS_CONFIGremains a configuration-discovery input, while the seven field-level operational environment overlays are retired;- read-only CLI commands expose ConfigKit's redacted effective configuration and per-path provenance explanations; and
- the direct
gopkg.in/yaml.v3dependency and the superseded custom parsing, presence, environment-overlay, and configuration-redaction code are removed.
The migration must preserve the fixed pipeline architecture, deterministic resolved-pipeline identity, detached ownership, fail-before-execution checks, secret exclusion, source-relative reference behavior, and offline validation requirements defined by the architecture and current integration contracts.
Ownership Boundaries
ConfigKit owns
- reading and bounding the selected YAML source, its declared imports, and the selected configuration profile after Notarius chooses them;
- composing imported definitions and applying the selected profile overlay;
- enforcing a single strict YAML document, supported scalar forms, unique mapping keys, and known typed fields;
- decoding into the application-defined configuration schema;
- distinguishing omitted input from explicit zero, false, empty, and null;
- applying typed application defaults;
- recording application normalization and derived defaults;
- attaching application validation diagnostics to semantic paths and source locations;
- detaching the successful configuration and its metadata;
- rendering the redacted effective configuration; and
- explaining the provenance of an effective or historical configuration path.
Notarius continues to own
- configuration-file discovery through
--config,NOTARIUS_CONFIG, and the installed default path; - the version policy and the application configuration structs;
- the meaning and validation of every Notarius field;
- pipeline, module, validator, artifact, and reference catalogs;
- conversion from configuration-facing values to runtime pipeline types;
- source-relative interpretation of configured external reference paths;
- run-time selectors and actions such as lane selection, input, resume, recomputation, session identity, and CLI reference changes;
- PromptKit profile discovery and validation; and
- redaction of resolved runtime structures that are outside ConfigKit's typed configuration snapshot, including exclusion of materialized reference content.
ConfigKit configuration profiles must not be confused with PromptKit LLM profiles or Notarius named pipeline definitions. ConfigKit does not become a module registry, pipeline resolver, command framework, or secret manager for PromptKit credentials.
Configuration Contract Version 5
Version spelling
Use the following root declaration:
version: 5
ConfigKit v0.2.0 accepts string or strict decimal-integer input at the exact
path selected by its VersionRule. It passes the original spelling to the
application checker and can decode an accepted integer into a string-backed Go
field. This narrow exception lets Notarius retain its existing numeric YAML
convention without weakening strict decoding elsewhere. Quoted version: "5"
is also accepted by ConfigKit, but the maintained Notarius spelling is the
unquoted integer shown above.
The version checker must reject absent, null, and unsupported identifiers with
safe, value-free diagnostics at /version. Version 5 is a clean break: the
loader must not decode or rewrite a version 4 document and must not retain the
legacy loader as a fallback.
Canonical module bindings
Every module and validator binding must use an object with a module field.
For example:
input:
module: seriatim
extract:
module: dnd/spells
merge:
module: appendorder
normalize:
module: dnd/spells
Version 5 does not accept scalar binding shorthand. Preserving shorthand would require a dynamic subtree or a compatibility pre-parser and would retain much of the complexity this adoption is intended to remove.
Canonical reference sources
Reference sources must also use explicit objects. An external file reference
uses a path field; a generated reference retains its typed artifact
selector. For example:
references:
party:
path: ./dnd-party.txt
npc_registry:
artifact:
step: describe-session
lane: npc-registry
Pipeline-level references remain external-file-only. Step- and binding-local references may select earlier artifacts according to the existing ordered handoff rules. Relative external paths resolve from the file that declared the surviving value, including imported fragments and configuration profiles. CLI reference paths continue to resolve from the process working directory.
Remove the lane-level references compatibility alias in version 5. New files
must place a lane's extraction references under extract.references. This
leaves one canonical location and removes merge-precedence behavior from the
file converter.
Strictness and dynamic options
Keep ConfigKit's strict YAML rules. Version 5 therefore rejects aliases, merge keys, unsupported tags and scalar forms, duplicate mapping keys, unknown typed fields, trailing documents, and incompatible YAML types. Do not recreate an application pre-parser to weaken these rules.
Module options remain map[string]any where the selected module determines
the valid keys. ConfigKit represents YAML integers in dynamic values as
int64. Module option validators must accept that canonical representation,
check range before conversion, and hand runtime modules a safe int only where
their APIs require it. Do not marshal and decode dynamic subtrees through YAML
to recover native int values.
ConfigKit-Backed Loading Model
Define a plain, typed configuration document for ConfigKit. It should mirror
the public file schema, use ordinary YAML tags, and contain no custom YAML
unmarshal hooks. Keep it separate from framework runtime types where doing so
avoids YAML concerns, presence flags, or configuration-only unions leaking into
internal/framework/pipeline.
The reusable ConfigKit definition should provide:
- fresh built-in defaults for each load;
- the
/versionrule; - recorded whitespace normalization for identifiers, paths, profile IDs, endpoints, and other fields whose current contract normalizes input;
- recorded derivation of
concurrency.stage_workers.extractfrom the effectiveconcurrency.total_llmonly when the worker value was omitted; - collision checks before normalizing map keys or cross-step identifiers;
- configuration-only semantic validation with precise paths and related paths where useful; and
- a dynamic secret predicate that covers credential-shaped keys below module option maps, preserving the current defensive redaction boundary even though ordinary Notarius configuration contains no credential values.
Use ConfigKit's resolver only for deterministic configuration normalization and derived defaults. Use its read-only semantic validator for domain constraints. Do not put module lookup, PromptKit profile lookup, filesystem resource checks, or runtime pipeline construction in either callback.
After a successful load, perform one explicit, independently tested conversion
from the canonical configuration document to the Notarius runtime Config and
pipeline profile types. The converter should be a narrow ownership boundary,
not a second parser or validation framework. It may translate enums and checked
numeric values, but it must not silently change already validated meaning.
Retain the ConfigKit result alongside the runtime configuration in an
application-owned loaded-configuration value. Commands that need only the
runtime Config should not gain access to parser internals; commands and debug
writers that expose safe configuration provenance may use the retained result.
Imports And Configuration Profiles
Enable ConfigKit's explicit imports using its existing configkit.imports
syntax, including path, glob, and optional declarations. Operators can keep
whole pipeline definitions in separate files and compose them through the
selected root file. The root file alone declares version.
Use ConfigKit's strict composition semantics. Imports combine disjoint
definitions; they do not provide ordered override layers or concatenate
sequences. In particular, one pipeline's ordered steps list remains together
in a single definition. Do not add Notarius-specific step merging or a second
import mechanism.
Expose --config-profile <name> on every command that loads configuration:
run, config validate, pipelines list, config effective, and
config explain. Pass the selection to ConfigKit. A missing selection uses
the composed base configuration. ConfigKit's reserved selection default
also means no overlay; it does not load a default profile file.
Use ConfigKit's profile location <configuration-root>/profiles/<name>.yml
and existing selection rules. A selected missing profile fails loading.
Profiles recursively overlay mappings and replace scalars, sequences, and
nulls as specified upstream. Development profiles can change pipeline-level
llm_profile, concurrency, and output settings without repeating the pipeline.
Changing an individual binding inside steps requires replacing the complete
steps sequence. Document this limitation prominently in examples.
Configuration profiles and PromptKit profiles are independent. The former
select a configuration overlay with --config-profile; the latter select LLM
settings through llm_profile and the existing --llm-profile override. Neither
selection implicitly chooses the other.
Use the selected file's canonical directory as ConfigKit's configuration root. Document the upstream requirement that imports and configuration profiles stay within that root, along with composition conflicts, cycle/repeated-import errors, and profile sequence replacement. This containment rule does not apply to Notarius input, reference, output, cache, debug, or PromptKit profile paths.
Paths And Source Ownership
Resolve relative external reference paths and promptkit.profile_file and
promptkit.profile_dir relative to the file that supplied the surviving value.
This deliberately changes PromptKit profile-source paths from their current
working-directory convention in version 5. Absolute paths remain absolute.
Keep output, cache, and debug relative-directory semantics unchanged; they are
runtime destinations rather than files bundled with configuration fragments.
Use ConfigKit's source metadata to identify each surviving declaration's origin, including profile replacements and values inherited from the base. Record path normalization so effective output and explanation reflect the paths used at runtime. Preserve attribution when identifier normalization renames an enclosing map key. Do not resolve overwritten or removed path values or infer source ownership from diagnostic display labels. Explicit CLI paths retain their working-directory interpretation.
Discovery, Precedence, And Overrides
Keep the existing file-discovery order: explicit --config, then non-empty
NOTARIUS_CONFIG, then the installed default path. NOTARIUS_CONFIG selects a
source and is not a field-level configuration override, so it remains outside
ConfigKit.
Retire these seven field-level environment overlays in version 5:
NOTARIUS_TOTAL_LLM_CONCURRENCY;NOTARIUS_STAGE_WORKERS_EXTRACT;NOTARIUS_OUTPUT_DIR;NOTARIUS_CACHE_CHUNK_PLANS_MODE;NOTARIUS_CACHE_CHUNK_PLANS_DIR;NOTARIUS_CACHE_CHECKPOINTS_DIR; andNOTARIUS_DEBUG_DIR.
ConfigKit deliberately does not model general environment overlays. Applying them after a successful load would make the retained effective configuration, origins, and explanations disagree with the values used at runtime. Operators must put durable values in YAML and use the supported explicit CLI flags for invocation-local changes.
Classify the current run flags by responsibility. A flag that directly
overrides a field in the typed configuration document should become a
ConfigKit Override record only when the operator explicitly supplied it;
parser defaults must not be submitted as overrides. Input selection, lane
selection, session identity, resume/recompute actions, command-line references,
and similar invocation state remain outside ConfigKit even when they affect
runtime resolution. The documented precedence becomes built-in defaults,
composed YAML sources, the selected configuration profile, explicit
configuration-valued CLI overrides, and then
invocation-only resolution actions at their existing application boundary.
Validation And Diagnostics
Move configuration-only checks currently split among parsing, file
application, and Config.Validate into the ConfigKit
definition when they can be expressed against the typed file contract. Report
all independently discoverable semantic errors in one validation pass rather
than failing at the first field, while avoiding cascaded diagnostics whose only
cause is an earlier invalid parent.
Keep catalog-dependent checks in Config.Resolve and
pipeline.ResolvePipeline. These include registered module and validator
existence, stage capabilities, artifact compatibility, reference-slot
compatibility, PromptKit profile availability, and the concrete resolved
pipeline digest. Their existing error classification and fail-before-prepare
behavior must remain intact.
At the CLI boundary, recognize *configkit.LoadError and use ConfigKit's
canonical diagnostic renderer. Operators should receive the diagnostic code,
semantic path, safe message, and source coordinates when available. Do not
append raw YAML parser errors, source fragments, configuration values, or
secret values to the outward error chain. File discovery/read context should
remain clear without wrapping the same failure repeatedly.
Nonfatal ConfigKit diagnostics, if Notarius defines any, must not be silently dropped. They should enter the existing process-warning model only when they represent an actionable configuration degradation. Ordinary normalization, defaulting, and provenance records are explanation data, not run warnings.
Effective Configuration And Redaction
Use Result.WriteEffective as the canonical renderer for the loaded effective
configuration. The requested debug bundle should store this deterministic,
redacted YAML rather than a separately maintained JSON clone of the raw
configuration. Update the artifact name and documentation together if the
format changes.
Use ConfigKit's secret predicate to redact credential-shaped dynamic option paths. PromptKit credentials remain environment references in PromptKit-owned profile files and must not be copied into Notarius configuration or debug state.
Do not remove Notarius's redaction of the resolved pipeline merely because the loaded configuration is ConfigKit-backed. The resolved value contains application-owned structures, selection results, and reference provenance that ConfigKit does not own; materialized reference bytes must continue to be excluded from ordinary summaries. Remove only manual configuration cloning and redaction made redundant by the frozen ConfigKit result.
Configuration Inspection Commands
Add these read-only commands as first-class ConfigKit-backed inspection surfaces:
notarius config effective [--config path/to/config.yml] [--config-profile name]
notarius config explain [--config path/to/config.yml] [--config-profile name] <json-pointer>
config effective loads the selected configuration through the production
path and writes only ConfigKit's deterministic redacted YAML to standard
output. config explain accepts one canonical JSON Pointer, loads through the
same path, and writes ConfigKit's deterministic redacted explanation for that
effective or historical path. Neither command resolves a pipeline, prepares a
module, requires provider credentials, or contacts a provider.
Both commands must use ordinary configuration discovery, ConfigKit diagnostic rendering, the same import and profile selection as execution, and the existing CLI exit-status conventions. An unknown or noncanonical explanation path is a command failure, not an empty successful result. Standard output contains only the requested inspection output; diagnostics go to standard error. Effective output is YAML and explanation is ConfigKit's human-readable text; neither is promised to be reloadable configuration or a new durable JSON API. There is no unredacted mode.
Required Code And Contract Changes
The implementation must address the following coherent work areas:
- add and pin a reviewed
gitea.maximumdirect.net/eric/ConfigKitrelease with v0.2.0's semantics and the required source-origin API; - introduce the version 5 typed document and its ConfigKit definition;
- replace
LoadFileConfig, customUnmarshalYAMLimplementations, manual strict-parser checks, presence flags,ApplyFileConfig, and redundant configuration cloning with the ConfigKit load boundary; - consolidate defaults, normalization, and configuration-only validation into the definition while retaining runtime catalog validation;
- update CLI configuration loading and error rendering without changing file discovery;
- enable ConfigKit imports and configuration profiles, wire
--config-profilethrough all configuration-loading commands, and retain declaration origins for relative file paths; - retire field-level environment overlays while retaining
NOTARIUS_CONFIG; - classify current run flags so only true configuration overrides use ConfigKit override records and invocation-only state remains outside the configuration loader;
- update dynamic module-option decoding and range checks for
int64; - integrate ConfigKit effective rendering and metadata into requested debug
bundles and the new
config effectiveandconfig explaincommands; - remove superseded code, tests, and the direct
gopkg.in/yaml.v3dependency after no production or test-only use remains; and - update maintained examples and canonical documentation in the same change as each externally visible contract adjustment.
Documentation And Migration
Update docs/config.md as the canonical owner of version 5 syntax, defaults,
precedence, canonical bindings and references, strict YAML behavior, and the
retirement of field-level environment overrides. Replace the version 3
migration section with a concise version 4-to-5 migration guide covering:
- changing the numeric version value from 4 to 5;
- expanding scalar module bindings into
{module: ...}objects; - expanding scalar external references into
{path: ...}objects; - moving lane-level reference aliases to
extract.references; - resolving configured PromptKit profile-source paths from their declaring file instead of the working directory;
- using imports for whole pipeline definitions and configuration profiles for overlays, including complete replacement of step sequences;
- replacing retired operational environment overrides with YAML or supported explicit CLI flags; and
- any effective/debug artifact or inspection-command changes.
Update docs/internal/configuration.md to describe the ConfigKit boundary,
typed document-to-runtime conversion, application-owned validation, retained
metadata, and catalog-resolution handoff. Update docs/cli.md only for changed
commands or diagnostic behavior, docs/operations.md only for operator-facing
configuration and debug workflows, and the relevant architecture text if the
adopted precedence model changes.
Convert both maintained D&D configurations and every configuration snippet to version 5 canonical syntax. Examples must remain secret-free and load through the same production path exercised by the CLI. Do not keep a second set of version 4 examples.
Keep the minimal D&D example self-contained. Extend the complete D&D example
with an imported whole pipeline definition and a selectable development
configuration profile. Keep configuration overlays physically distinct from
the maintained PromptKit profile files so readers can see which system owns
each file. Test the complete example both with and without the development
overlay, and update subprocess guidance to show optional --config-profile
selection. All configuration inspection commands must document what the
selected profile changes and that runtime pipeline resolution is separate.
Because this change establishes a durable dependency and reallocates ownership at a core boundary, add an ADR recording the decision to use ConfigKit, the responsibilities retained by Notarius, the deliberate version 5 break, and the retirement of the application environment-overlay layer. The rejected alternatives should include continuing the custom loader, retaining version 4 temporarily, or wrapping ConfigKit in a compatibility pre-parser. The ADR should record rationale, not duplicate the current configuration reference or implementation details.
Testing And Verification
Follow the repository's behavior-focused testing policy. Reuse ConfigKit's own tests for generic YAML and metadata mechanics; Notarius tests should protect only its contract and integration responsibilities.
Required Notarius coverage includes:
- successful loading of omitted, explicit zero/false/empty, and derived values that materially affect Notarius behavior;
- numeric and quoted version 5 acceptance and safe rejection of absent, null, version 4, unsupported identifiers, and non-integer numeric forms;
- canonical binding and reference decoding plus rejection of former shorthand;
- identifier normalization and normalized-collision rejection;
- aggregation and source location of representative application validation errors;
- unchanged configuration discovery and source-relative reference resolution;
- imported whole pipeline definitions, representative composition conflicts, and profile precedence through the real ConfigKit loader;
- profile sequence replacement and explicit clearing, so Notarius does not accidentally append or merge ordered steps;
- relative reference and PromptKit profile-source paths from root files, imported fragments, and overlays, including an unchanged inherited value, a replaced value, and an enclosing normalized identifier;
- matching configuration-profile selection across execution, validation, pipeline listing, effective output, and explanation, without provider calls;
- checked dynamic integer conversion at each production module option boundary;
- detached ownership of loaded configuration and resolved pipelines;
- safe CLI rendering of representative ConfigKit parse, decode, version, resolution, and validation failures;
- successful redacted
config effectiveoutput and effective, historical, unknown, and noncanonicalconfig explainpath behavior; - effective-configuration redaction, including nested credential-shaped dynamic option keys, without exposing materialized references;
- maintained minimal and complete example validation through the production loader and production module catalog; and
- representative end-to-end CLI validation and pipeline resolution without network access or credentials.
Delete or rewrite legacy parser tests whose only purpose is to retest behavior owned by ConfigKit. Retain a small integration sample for each strictness rule that is itself part of the Notarius public contract, and avoid assertions on incidental diagnostic prose.
The completed implementation must pass:
go test ./...
go vet ./...
go build ./cmd/notarius
It must also validate both maintained example files with the production CLI. The complete example may use a placeholder credential for offline PromptKit profile validation, as documented today; no live provider call is required.
Non-Goals
- Moving pipeline resolution, module catalogs, or PromptKit profiles into ConfigKit.
- Adding arbitrary environment-to-field binding.
- Retaining the retired field-level operational environment variables as a parallel precedence layer.
- Loading credentials into the Notarius configuration document.
- Supporting remote configuration sources or formats other than YAML.
- Adding a generic dynamic-subtree conversion layer.
- Composing ordered steps across fragments or adding application-specific sequence merge, profile inheritance, or profile stacking semantics.
- Preserving scalar shorthand with an application pre-parser.
- Changing the fixed pipeline shape or durable extraction artifacts.
- Changing run input, output publication, cache, checkpoint, retry, or warning semantics except where configuration loading or debug rendering directly exposes them.
Acceptance Criteria
- All configuration-loading commands use a pinned ConfigKit release providing the required source-origin API, building on v0.2.0, and no production code directly imports a YAML parser.
- Version 5 is the only accepted file contract, documents
version: 5as its canonical spelling, and has one canonical object representation for module bindings and references. - The ConfigKit definition is the sole owner of typed defaults, input presence, recorded normalization, derived configuration defaults, and configuration-only semantic diagnostics.
- Notarius remains the sole owner of catalog-dependent resolution and runtime conversion.
- Dynamic option integers are handled without unchecked narrowing or YAML round-trips.
- CLI diagnostics and effective configuration output are deterministic, redacted, value-safe, and retain useful path/source context.
config effectiveandconfig explainexpose only ConfigKit's redacted inspection views and perform no pipeline or provider work.- Imports compose whole pipeline definitions and selected configuration profiles apply ConfigKit's documented overlay semantics consistently across all configuration-loading commands.
- Relative external references and PromptKit profile-source paths use the surviving declaration's source directory, and safe inspection agrees with the paths used at runtime.
- The resolved pipeline remains detached, reference content remains excluded from ordinary summaries, and its identity changes under the same semantic conditions as before.
- Maintained examples, current documentation, focused tests, and repository-wide validation agree with the implemented version 5 contract.
NOTARIUS_CONFIGstill participates in discovery, while the seven field-level operational environment variables no longer affect loaded configuration.- Superseded custom parser, presence, environment, clone, redaction, and test code is removed rather than left as an alternate loading path.
Deferred Composition Work
Step-level composition may be revisited after operators use imported whole pipeline definitions. It is not part of this adoption: ConfigKit does not concatenate sequences or merge their elements by application-defined IDs. Any future extension must define ordering, duplicate step IDs, dependency validation, profile overrides, and provenance before changing that contract.