409 lines
24 KiB
Markdown
409 lines
24 KiB
Markdown
# Architecture Policy
|
|
|
|
**Base Policy Version:** 1.3
|
|
|
|
**Last Updated**: 2026-10-22
|
|
|
|
**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 [future feature list](../roadmap/future.md) preserves
|
|
candidates for future consideration.
|
|
|
|
## 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.
|
|
|
|
The collection-policy and CLI-ordering requirements below reflect accepted
|
|
[ADR 0017](../adr/0017-collection-sequence-policy.md) and
|
|
[ADR 0018](../adr/0018-cli-after-structural-resolution.md). The
|
|
[sequence roadmap](../roadmap/roadmap.md) tracks the work to implement them;
|
|
the configuration contract continues to describe implemented behavior.
|
|
|
|
## 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.
|
|
|
|
## Simplicity, Defaults, And Explicit Controls
|
|
|
|
These principles apply throughout ConfigKit: to configuration syntax, the public
|
|
Go API, defaults, and feature design. Evaluate their effects on both configuration
|
|
authors and consuming applications.
|
|
|
|
**Keep straightforward workflows straightforward.** Common configuration
|
|
workflows should require little configuration text and minimal API setup.
|
|
Support more demanding workflows through explicit, composable options, without
|
|
requiring simple consumers to understand or configure those options.
|
|
|
|
**Prefer simple, permissive defaults with explicit controls.** Choose predictable
|
|
defaults that minimize required setup and accommodate valid configurations
|
|
without unnecessary restrictions. Provide selectable options for workflows that
|
|
need additional capabilities, narrower behavior, or stronger requirements.
|
|
|
|
Measure simplicity by what consumers must understand and maintain, including
|
|
configuration text and application-side plumbing. Concise syntax is useful, but
|
|
hidden inference and interacting meanings can make a shorter declaration harder
|
|
to understand. Prefer explicitness where it makes behavior more predictable.
|
|
A terse configuration format that requires substantial application-side
|
|
infrastructure does not satisfy the simplicity principle.
|
|
|
|
Define what permissiveness means for each behavior. Allowing an optional
|
|
contribution to be absent or accepting valid inputs without an unnecessary
|
|
filename restriction can reduce required setup. Invalid declarations, ambiguous
|
|
composition, and unreadable selected sources must still produce meaningful
|
|
errors under the applicable contracts. Permissiveness does not authorize silent
|
|
recovery from invalid supplied configuration or weaken configuration integrity.
|
|
|
|
Options should express coherent choices with understandable purposes and fit
|
|
ConfigKit's shared configuration model. Support powerful controls when they
|
|
serve concrete needs; these principles do not require a switch for every
|
|
implementation behavior or conceivable consumer preference.
|
|
|
|
Defaults require judgment when simplicity and permissiveness pull in different
|
|
directions. Broader discovery can accommodate more layouts while increasing the
|
|
implicit search scope. Evaluate such tradeoffs explicitly; these principles do
|
|
not mechanically select the broadest behavior for every option. Changes to
|
|
existing defaults remain subject to compatibility review, and proposed behavior
|
|
remains in the roadmap until implemented.
|
|
|
|
## 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`.
|
|
Import discovery is available only through declarations in selected sources:
|
|
explicit `path`, `dir`, and `glob` selectors may search their declared scope, but
|
|
ConfigKit does not search conventional application paths or discover a main
|
|
configuration file implicitly. Imports and sibling inheritance share
|
|
dependency-ready scheduling within their existing scopes; acquisition order and
|
|
precedence-tier boundaries remain distinct. The [configuration contract](../config.md#explicit-imports)
|
|
owns observable behavior; [ADR 0015](../adr/0015-shared-dependency-ready-ordering.md)
|
|
and [ADR 0016](../adr/0016-independent-import-controls.md) record the decisions.
|
|
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).
|
|
|
|
### Accepted Processing Order
|
|
|
|
Complete source composition and precedence, including selected overlays, before
|
|
sibling inheritance. Then apply typed defaults and decode the configuration.
|
|
Apply explicit CLI overrides to that configuration and check their types before
|
|
running application resolution. Effective secret lookup and final validation
|
|
follow resolution, before publication. [ADR 0018](../adr/0018-cli-after-structural-resolution.md)
|
|
records this ordering.
|
|
|
|
CLI overrides are the highest-priority input to application resolution. Supplied
|
|
sequences replace whole sequences, and mappings combine recursively. Inheritance
|
|
and typed defaulting do not run again after CLI application. CLI paths and values
|
|
must not introduce structural directives; earlier source, inheritance, and
|
|
decoding failures remain errors even if a CLI assignment would replace the value.
|
|
Semantic validation assesses the final configuration after overrides and resolution.
|
|
|
|
Applications own flag parsing and domain resolution. ConfigKit owns override
|
|
application, validation, and source attribution. Resolution receives CLI values
|
|
with their explicit-presence and source information. Absence-based defaults
|
|
preserve explicit values; recorded normalization and derivation can still change
|
|
them. Typed defaults and application resolution retain their own assignment
|
|
semantics, independently of collection operations during source assembly.
|
|
|
|
## 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. Sequences with an applicable identity
|
|
combine distinct records and reject duplicate identities, including equal
|
|
records. Other overlapping non-mapping values remain conflicts, including equal
|
|
values. Conflicting identity or operation declarations among peers also fail.
|
|
Traversal order establishes stable reporting and record order; it does not grant
|
|
precedence to peers. [ADR 0001](../adr/0001-separate-composition-and-overlay.md)
|
|
records the distinction between strict composition and intentional overrides.
|
|
|
|
The main document and its imports form a strictly composed tier. Explicit
|
|
underlay groups compose strictly within each group, then combine from lowest to
|
|
highest beneath the main tier. Between tiers, mappings combine recursively and
|
|
retain omitted lower-priority children. Sequence values follow collection policy;
|
|
other present values retain the established replacement rules. Source acquisition
|
|
kind does not determine precedence. Input-specific constraints, including version
|
|
ownership and path protections, remain part of the configuration contract.
|
|
Consistent combination semantics do not require identical acquisition or lifetime
|
|
behavior from every source kind.
|
|
|
|
Built-in sibling `extends` relationships resolve after source precedence in the
|
|
parser-independent document representation. Preserve existing mapping-sibling
|
|
scopes and allow an identified sequence to provide a sibling scope for its
|
|
records. A record may inherit from one parent in that sequence, selected by
|
|
complete identity. The child supplies and keeps its own identity while inheriting
|
|
the parent's other settings. Parents remain ordinary records subject to normal
|
|
validation.
|
|
|
|
Use the shared dependency ordering and preserve assembled sequence order.
|
|
Resolve enclosing relationships before nested destination-local relationships.
|
|
Mappings combine recursively; sequences use the inheriting collection's policy.
|
|
Whole-record upsert replaces records during combination; explicit inheritance
|
|
uses the records that remain. Inheritance remains built-in and creates no
|
|
acquired source.
|
|
[ADR 0009](../adr/0009-sibling-inheritance.md) records the
|
|
original scope and continuing ownership decisions, with sequence combination
|
|
and identified-sequence inheritance defined by
|
|
[ADR 0017](../adr/0017-collection-sequence-policy.md) and CLI ordering by
|
|
[ADR 0018](../adr/0018-cli-after-structural-resolution.md).
|
|
|
|
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.
|
|
This priority order is distinct from processing order: typed defaults are applied
|
|
at decoding, and application resolution follows CLI input under the lifecycle
|
|
rules above.
|
|
|
|
Preserve node-based composition through source assembly and inheritance until
|
|
typed decoding. Preserve presence and source information through later CLI and
|
|
application-resolution changes as well. 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.
|
|
|
|
## Collection Identity And Combination
|
|
|
|
ConfigKit owns a common collection policy for imported records, inline sequences,
|
|
selected overlays, and sibling inheritance. Configuration authors may declare
|
|
identity and combination rules; applications may register authoritative identity
|
|
constraints. Configuration declarations must agree with application constraints.
|
|
Keep source placement explicit and separate from policy, retaining the placement
|
|
decision in [ADR 0014](../adr/0014-sequence-placement-and-composition.md).
|
|
[ADR 0017](../adr/0017-collection-sequence-policy.md) owns the rationale for
|
|
collection policy and its relationship to application authority.
|
|
|
|
Gather declarations for the relevant destination and source or item-local scope
|
|
before strict composition. Applicable identity from accumulated lower tiers must
|
|
be available when composing higher-tier peers. Higher-tier declarations cannot
|
|
repair invalid lower-tier composition. Preserve the distinction between source
|
|
acquisition, dependency-ready scheduling within scopes, and precedence order.
|
|
|
|
Strictly compose each tier's contributions before applying one operation for the
|
|
collection against its lower value. Support whole-sequence replacement,
|
|
duplicate-rejecting append, and whole-record replacement by identity (`upsert`).
|
|
Append and upsert require identity; do not infer it from record fields or position.
|
|
Duplicate identities among peers fail under every mode. Source precedence and
|
|
inheritance use the same operations and deterministic ordering rules. Whole-record
|
|
upsert replaces the matching record's fields together; recursive record merging
|
|
and unkeyed sequence concatenation are outside this operation model.
|
|
|
|
An explicit mode persists at its applicable destination until a higher explicit
|
|
mode changes it. With no explicit mode, effective identity selects upsert and
|
|
its absence selects whole-sequence replacement. Inferred defaults do not become
|
|
persistent instructions. The inheriting destination chooses its operation; copying
|
|
a parent collection does not automatically copy its mode to that destination.
|
|
|
|
Track YAML identity and explicit modes according to their separate lifetimes.
|
|
Actual whole-value replacement discards lower YAML identity while preserving
|
|
application constraints and any new identity declaration. Item-local policies
|
|
belong to their records: retaining a record retains its rules, and discarding it
|
|
discards those rules. Policies independently applicable at a destination remain
|
|
in effect. Inheritance can copy YAML identity subject to agreement and replacement
|
|
rules; application registrations continue to constrain their registered paths.
|
|
Numeric positions alone do not establish policy ownership.
|
|
|
|
Allow declarations without accompanying records. A key declaration validates a
|
|
retained sequence immediately or remains pending in its owning scope when no
|
|
sequence exists. A mode-only declaration affects later combinations without
|
|
recombining earlier tiers. Neither declaration alone clears a collection, and
|
|
declaring replace alone does not authorize changing a retained collection's
|
|
identity. These rules do not independently impose path-existence or shape
|
|
requirements.
|
|
|
|
Enforce YAML identity during source composition, precedence, and identified
|
|
inheritance operations. Application registrations additionally constrain final
|
|
effective sequences after defaults, CLI overrides, and application resolution.
|
|
The application schema and validation own existence, nullability, and allowed
|
|
shape. The final identity guarantee covers actual sequences on successful load;
|
|
it does not cover every intermediate callback state or later caller mutations.
|
|
YAML-only declarations carry no such final guarantee through defaults, CLI
|
|
assignments, or application resolution.
|
|
|
|
Preserve source attribution, presence, history, sensitivity, and resource context
|
|
as records survive, move, or are replaced. Identity diagnostics use paths and
|
|
available origin information without exposing identity values. The guarantee
|
|
requires no new mutation-history system or exact reconstruction of causes for
|
|
records supplied by defaults or callbacks. Exact identity equality, declaration
|
|
syntax, presence cases, and operation tables belong in the configuration contract
|
|
when implemented; the roadmap owns those details until then.
|
|
|
|
## 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.
|
|
Record significant decisions to exclude capabilities, and their rationale, in
|
|
ADRs. Significant boundary changes require an ADR and corresponding
|
|
policy/contract updates; implementation convenience alone is not a reason to
|
|
broaden the public API.
|