188 lines
10 KiB
Markdown
188 lines
10 KiB
Markdown
# Public facade and internal implementation
|
|
|
|
Status: agreed direction; not implemented. This roadmap defines the desired end
|
|
state and user intent. The [implementation plan](implementation.md) owns the
|
|
ordered work, concrete adapter design, validation commands, 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.
|
|
|
|
## Current architecture
|
|
|
|
The root currently owns both public declarations and most private implementation:
|
|
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.
|
|
|
|
## Target 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 criteria
|
|
|
|
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.
|