Plan the public facade refactor
This commit is contained in:
187
docs/roadmap/public.md
Normal file
187
docs/roadmap/public.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user