# Architecture Policy **Base Policy Version:** 1.3 **Last Updated**: 2026-09-29 **Upstream Source:** [Standard Repository Policies](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/architecture.md) ## Purpose And Ownership The adopted policy defines the application's purpose and the architectural constraints contributors must follow. It owns system boundaries, responsibility and dependency rules, established invariants, and explicit non-goals. Describe the concrete package inventory and implementation mechanics in internal documentation. Link to canonical public contracts for their exact behavior and to ADRs for decision rationale. ConfigKit is an opinionated Go library for consistent YAML configuration loading and resolution across consuming applications. The [component overview](../internal/overview.md) owns implementation inventory, the [configuration contract](../config.md) owns implemented behavior, and the [deferred scope](../roadmap/deferred.md) preserves deferred scope. ## Adopted Constraints And Implemented Structure Architectural constraints govern current and future development. Record implemented structure accurately, proposals and implementation status in the roadmap, and significant decisions and rationale in ADRs. Describe a target structure as implemented when its changes land. Constraints must reflect deliberately selected requirements and decisions. Do not promote incidental implementation choices into architectural invariants. ## Success Criteria ConfigKit prioritizes a coherent configuration model: consistency and predictability take precedence over flexibility for accommodating individual downstream conventions. Consumers should learn one set of rules, not different combination or presence semantics depending on the source format or consuming application. Distinct operations such as strict composition and intentional overlay express distinct purposes within that model. Consumers should be able to load once and receive a complete, validated configuration with inspectable provenance for its final values, including defaults and recorded application-derived values. Configuration and metadata together must distinguish explicit zero, false, empty, and null input from omission; ordinary Go values alone need not encode those distinctions. Application-owned resolution belongs within the recorded loading lifecycle, so consumers need no subsequent generic merging, defaulting, or provenance reconstruction. This does not transfer domain execution or runtime resource management into ConfigKit. 1. **Reduce application infrastructure:** Can applications delete substantial configuration infrastructure while retaining clear ownership of their domain rules? 2. **Unify configuration conventions:** Do consumers adopt one consistent configuration model, with only the flexibility needed to express genuine application requirements? Both criteria must hold. Minimize consumer loading and processing code subject to the consistency, presence, and provenance principles above, rather than trading those principles for consumer-specific shortcuts. ConfigKit should not preserve divergent consumer conventions merely for compatibility, nor impose restrictions that prevent useful domain information from being expressed. Prefer a canonical representation using existing primitives before adding another mechanism. Extend the model when a concrete requirement cannot be accommodated cleanly, not simply because a consumer already implements a different convention. Use both criteria to evaluate public API changes and consumer adoption. Shared loading, presence tracking, source attribution, and diagnostic plumbing should move out of applications; application types, defaults, normalization, and semantic validation should remain recognizable application code. Replacing that plumbing with equally repetitive callback bookkeeping does not meet the first criterion. Offering alternate parsing, precedence, or composition conventions through escape hatches does not meet the second. Consumer migrations are expected and may intentionally change configuration syntax. Existing consumer code supplies implementation foundations and evidence of needs; it does not define ConfigKit's compatibility obligations. Once ConfigKit publishes its own contracts, changes to those contracts remain governed by the [release policy](release.md). Require representative simple and complex consumer examples to substantiate API designs. Proposed extensions must identify the domain requirement, explain why existing primitives cannot accommodate it cleanly, and show how the extension preserves a coherent model. Application-specific interpretation and validation remain appropriate. Reuse Narratio's applicable infrastructure under these boundaries, and keep simple consumers independent of optional composition features. The [consumer adoption guide](../consumers/README.md) demonstrates the implemented surface against representative simple and complex shapes. ## Responsibility Boundaries ConfigKit owns configuration input processing and the coordination of decoding, resolution, validation, and inspection. Applications own their configuration types, field meanings, defaults, normalization, semantic validation, and schema version policy. Applications also own main-file search conventions, CLI wiring, remote acquisition, runtime initialization, and resource checks. The engine applies each input's mapping destination within the existing source assembly, applies immediate-parent-relative YAML destinations to imports, and retains each effective placement with source identity and inspection records; the root facade owns the public fields and conversions. The [configuration contract](../config.md#input-destinations) defines the observable placement behavior. [ADR 0011](../adr/0011-input-destinations.md) records input placement, and [ADR 0012](../adr/0012-relative-import-destinations.md) records the import extension. Library code must not depend on consuming applications or initialize their runtime resources. It must not perform implicit network access, discover application-specific paths, or change process-global state during ordinary `Load`. The explicit secrets-directory bootstrap is a narrow, opt-in exception: applications invoke it during startup, and it reports writes, skips and partial failures. It must never run implicitly as part of configuration loading. Remote data can enter through application-supplied input without introducing a network provider into the library. ## Loading Lifecycle Configuration loading is a synchronous, complete-on-return operation. A successful load provides the final configuration and inspection metadata after the configured processing and validation phases complete. A failed load reports diagnostics and returns no usable partial result. The normal application pattern is one load near startup; applications may explicitly initiate additional loads. ConfigKit releases its library-owned loading handles before returning and remains inactive until the application explicitly invokes it again. It performs no background watching, refresh, or mutation of published results. Caller-owned resources and application callback effects remain the caller's responsibility. The [configuration contract](../config.md#completion-and-resource-lifetime) owns the precise completion and ownership behavior. The proposed decision record is [ADR 0008](../adr/0008-complete-on-return-loading.md). ## Public API And Dependency Direction Keep one small, coherent public surface for the supported lifecycle. Simple single-document consumers must not need import, overlay, or provider machinery. Do not expose parser-specific nodes, errors, or extension hooks through the public API. Parser integration belongs behind a private boundary, as recorded in [ADR 0002](../adr/0002-own-yaml-semantics.md). The root `configkit` package is the sole supported consumer import path. Public named types stay root-owned and may hold private delegated implementation state; their exported signatures, fields, and method sets must not expose internal types or acquire internal methods through anonymous embedding. Root production code may depend inward on a cohesive engine and its supporting internal packages, but internal production packages must not import the root. Explicit forwarding and local conversion are acceptable where they preserve type identity, ownership, callback lifetime, and inspection behavior. The accepted root-wrapper boundary and its migration rationale are recorded in [ADR 0006](../adr/0006-public-facade.md); the implemented layout is described in the [component overview](../internal/overview.md). Separate input access, document processing, composition, decoding, metadata, and rendering responsibilities without prescribing an interface or package for each. Dependencies must preserve those boundaries; package splits and abstractions must earn their complexity. Do not impose a full application or hexagonal framework on this library. ## Configuration Integrity Within a configuration tier, independent contributions use strict composition: shared mappings combine recursively, while overlapping non-mapping definitions are conflicts, including equal values. Between precedence tiers, intentional overlay combines mappings recursively, retains omitted lower-priority children, and replaces other present values according to the existing overlay rules. Tier precedence is explicit; traversal order within a tier establishes stable reporting, not permission to override. The distinction and its rationale are recorded in [ADR 0001](../adr/0001-separate-composition-and-overlay.md). The main document and its imports form a strictly composed tier. Explicit underlay groups compose strictly within each group, then combine recursively from lowest to highest beneath the main tier. Source acquisition kind does not determine its precedence role. Input-specific constraints, including CLI path rules and opt-in version ownership, remain part of the configuration contract. Consistent combination semantics do not require identical acquisition or lifetime behavior from every source kind. After source precedence is complete, built-in sibling `extends` relationships resolve structurally in the same parser-independent representation, using the existing recursive overlay rules. This adds neither an acquired source nor a consumer registration mechanism. Enclosing relationships precede nested destination-local resolution; typed defaults, application resolution, and secret lookup retain their existing lifecycle roles. The [configuration contract](../config.md#sibling-inheritance) owns the exact syntax and presence rules, and [ADR 0009](../adr/0009-sibling-inheritance.md) records the rationale. ConfigKit must define supported YAML semantics explicitly rather than inherit parser defaults. Processing order and diagnostics must be deterministic for the same inputs and options. Invalid input must not produce a successful usable configuration. Do not run application hooks on invalid typed configuration. User-controlled precedence is application defaults, ordered underlays, composed main input, selected overlay, then explicitly supplied CLI overrides. Applications own flag parsing; ConfigKit owns overlay semantics and provenance. Domain resolution follows these inputs and must remain recorded, not become a hidden override channel. Preserve node-based composition until the effective input is ready for typed decoding. Preserve presence and source information through processing. Ordinary Go zero values must not be used to infer whether input was absent. Final configuration, metadata, and effective inspection must agree, including application resolution changes; see [ADR 0003](../adr/0003-preserve-presence-and-resolution-history.md). Safe historical values and an explanation renderer are part of the implemented contract, including superseded/deleted paths and effective source attribution. Inspection must agree with the final typed snapshot and apply historical sensitivity rules. See [ADR 0005](../adr/0005-explicit-overrides-and-safe-explanation.md). Detailed syntax and operation tables belong to the [current configuration contract](../config.md). The frozen inspection boundary also supports comparing completed same-type results without reopening sources or rerunning the load lifecycle. The [configuration contract](../config.md#effective-configuration-diffs) owns comparison and disclosure behavior, the [component overview](../internal/overview.md) owns implementation responsibility, and [ADR 0010](../adr/0010-effective-configuration-diff.md) records the accepted decision. ## Ownership And Confidentiality Returned configuration and metadata must not share mutable state across loads. ConfigKit must not mutate a result after returning it. Ordinary exported Go values are not language-enforced immutable objects; document caller ownership and the lifetime of captured metadata without promising otherwise. Secret values enter configuration through typed environment-name references; literal credentials are not a supported configuration convention. Only an explicit accessor exposes a resolved credential to application code. Credential bytes must never enter composition trees, history, or inspection. General environment-variable configuration overlays are an explicit non-goal. Bootstrap must preserve already set variables, including empty ones, and report blocked files without contents. See [ADR 0004](../adr/0004-secret-references-and-explicit-bootstrap.md). Secrets must be considered throughout error handling and inspection. Library renderers must apply secret classification, and parser errors must not be forwarded blindly when they can include raw input. Do not log configuration implicitly. Applications remain responsible for classifying their secrets and for safe messages supplied by their hooks. ## Scope Boundaries Do not introduce automatic directory discovery, network providers, arbitrary merge strategies, additional configuration languages, or application runtime management without concrete consumer evidence and an explicit design decision. The [deferred scope](../roadmap/deferred.md#explicit-non-goals-and-deferred-scope) owns the deferred-feature inventory. Significant boundary changes require an ADR and corresponding policy/contract updates; implementation convenience alone is not a reason to broaden the public API.