# Public facade and internal implementation Status: implemented on 2026-09-12. This roadmap records the intended architecture and user intent; the completed [implementation plan](implementation.md) records the ordered work, validation, and handoffs. ## Intent Make ConfigKit easier for humans and coding agents to navigate by separating its consumer-facing API from its implementation machinery. Consumers should find a readable, documented public surface in the root `configkit` package; maintainers should find the loading and inspection machinery under `internal/`. Long-term maintainability is the primary objective. The public API should remain stable while internal implementation can evolve without becoming a consumer commitment. An internal implementation change must not expand or alter the public API merely because the facade exposes the same underlying type. Repository aesthetics and easier navigation are useful secondary benefits, not the reason to minimize public-package code. This is an organizational and API-boundary refactor, not a new configuration feature or security boundary. Unexported root-package details are already private under ordinary Go access rules. The concern is accidentally exposing internal types or exported methods that consumers can depend on even without documentation. Wrappers provide structural separation; they do not make internal changes that alter observable public behavior automatically compatible. The [architecture policy](../policy/architecture.md) and [implementation discipline](../../AGENTS.md#implementation-discipline) govern the design. Existing [configuration contracts](../config.md) and exported Go declaration comments remain authoritative. The user-approved wrapper boundary is a contributor design choice, not a strengthening of consumer behavior contracts. ## Implemented architecture The root owns public declarations, records, wrappers, and deliberate forwarding. `internal/engine` owns source acquisition, imports, composition and overlays, reflection descriptors, decoding, resolution and validation coordination, provenance, history, secret handling, and rendering. The [component overview](../internal/overview.md) owns the detailed inventory. Two established internal boundaries already exist: `internal/document` owns the parser-independent tree, and `internal/yaml` owns upstream YAML integration. [ADR 0002](../adr/0002-own-yaml-semantics.md) remains applicable. Public types are not merely passive records. For example, `Result[T]` combines its public `Config` field with private inspection snapshots; `Resolution` holds callback state; `Path` holds semantic segments; and `Secret` holds private reference and resolved-value state. Moving implementation requires deliberate type ownership, not just file relocation. ## Architecture ### Public root package The root remains the single supported consumer import path. It contains: - package and API reference documentation; - public-owned type declarations, including wrappers and ordinary public records; - public constants, constructors, entry points, and lightweight delegation; - public contract tests and executable API examples where appropriate. Substantial source processing, decoding, lifecycle bookkeeping, history replay, and rendering mechanics live internally. Small value operations or adapters may remain in the root when moving them would introduce disproportionate machinery. Public API documentation remains beside public declarations and must be useful through ordinary Go documentation and editor navigation. ### Public-owned types and private delegation The agreed approach is root-defined public types, with private delegation to internal implementation where needed. Public wrappers expose deliberately chosen methods; internal state is held privately rather than exported or embedded in a way that promotes internal methods into the public surface. Public signatures and fields do not expose internal plumbing types. Adding an internal exported method must not add a public method automatically. Simple public records and small value operations can remain directly implemented in the root. There is no requirement to wrap every value or move every method. Aliases to internally owned implementation types are not the facade strategy: they would couple the public method set to the implementation this work is meant to separate. Straightforward forwarding code is an acceptable maintenance cost. Adapters must avoid competing mutable state and unnecessary conversions, particularly around `Result[T].Config`, frozen inspection data, callback lifetime, source ownership, and reflection-sensitive secret handling. These connections are private implementation details, not public bridge APIs or a generic registration framework. ### Internal engine and supporting packages A cohesive internal engine owns the loading lifecycle and the closely coupled schema, decoding, resolution, provenance, and inspection implementation. The selected package is `internal/engine`. The existing document and YAML packages retain their responsibilities. Additional packages are appropriate only where a cohesive responsibility and practical dependency boundary justify them. There is no requirement for one package per phase, file category, or component in the current inventory. Dependencies run from the facade into internal implementation and from the engine into its supporting packages. Internal packages do not import the public root. Public types remain root-owned; internal collaborators use internally owned state and narrow implementation connections. Internal code does not gain direct access to private fields of public types across packages. The engine may initially remain fairly large. A smaller root alone does not justify a network of adapters, generic provider interfaces, or a framework. Internal exports needed between packages are implementation surfaces, not new supported consumer APIs. Existing parser isolation remains intact. ## Compatibility and scope The target is a behavior-preserving refactor requiring no consumer migration: - Preserve the module/import path, exported names, function and method signatures, generic use, public fields, and supported construction patterns. - Preserve loading order, source ownership, defaults, composition and overlay behavior, resolution and validation semantics, and structured diagnostics. - Preserve callback lifetime, fixed entry provenance, detached query results, final inspection snapshots, and independence across loads. - Preserve secret recognition, credential timing, explicit access, redaction, and opt-in bootstrap behavior. - Preserve the supported Go and platform baseline and the existing dependency isolation; this work does not require a toolchain or third-party dependency change. Type restructuring can affect more than compilation. The implementation must assess method sets, interface satisfaction, zero values, copying and equality where supported, error assertions, reflection-based special-type recognition, and public documentation. Keeping public named types in the root preserves their defining package identity, but does not by itself prove compatibility of their state or behavior. Observable effects must be evaluated and reported, not silently declared compatible or newly guaranteed forever. No new input restrictions, resource limits, lifecycle restrictions, security guarantees, configuration semantics, or public extension hooks are in scope. Do not expose plumbing publicly merely to solve an import cycle. A discovered need to change an established contract requires a separate explicit decision. Version selection and publication remain governed by the [release policy](../policy/release.md), not by this roadmap. ## Completion evidence Completion means the root is a legible public surface, substantive plumbing is internally owned, consumers require no migration, and representative load-to-inspection workflows still pass through the facade. Public exported members are deliberate declarations, not incidental exposure of engine methods or state. The completed architecture has one engine, no parallel implementation, and no competing mutable configuration state. Small operations deliberately retained in the root are justified by maintainability, not a numerical size gate. Public behavior and maintained consumer examples exercise the root facade; mechanism tests live with their implementation owners. Test-only access does not expand the public API. Repository checks preserve parser isolation and the public boundary. Validation follows the [testing policy](../policy/testing.md); release-candidate validation remains separate under the [release procedure](../release.md). Tests should protect behavior and intentional dependency boundaries, not exact private layouts, forwarding call sequences, or an arbitrary package count. Implementation completion must be backed by evidence; this proposal is not evidence that the target architecture already exists. Completed roadmap material is retired during release preparation under the documentation policy. ## Documentation ownership in the target architecture Under the [documentation policy](../policy/documentation.md), architecture policy owns the public-wrapper and inward-dependency rules, preserving proportionality. An ADR records the accepted decision and why an alias-first facade was rejected: it reduces forwarding but couples public methods to internal implementation. The additional wrapper code is justified by long-term API independence. The component overview describes actual facade, engine, supporting packages, and state ownership. Development navigation locates the tests and mechanisms at their new owners. Internal documentation explains the private adapters. Exported API reference comments remain in the public package; consumer guidance identifies the supported root import without requiring knowledge of the engine. Configuration semantics remain in the existing contract, not in package-layout documentation. Internal layouts do not become public stability guarantees. Release documentation describes the behavior-preserving refactor without replacing canonical API or architecture documentation. The alias-versus-wrapper decision is resolved by the user's explicit selection of wrappers. An implementation convenience is not authority to change that decision or an established consumer contract.