104 lines
5.4 KiB
Markdown
104 lines
5.4 KiB
Markdown
# ADR 0003: Own public contracts through a root facade and internal CLI adapter
|
|
|
|
## Status
|
|
|
|
Accepted.
|
|
|
|
## Date
|
|
|
|
2026-10-06
|
|
|
|
## Context
|
|
|
|
CommandKit needs room to change its internal implementation details while
|
|
consumers retain access to a stable external API. Exposing backend assembly or
|
|
extension structures would let applications ossify around implementation
|
|
details that maintainers may later decide to replace.
|
|
|
|
ConfigKit, PromptKit, and ReleaseKit demonstrate facades that own public types
|
|
and delegate through private state and explicit conversion. ConfigKit also
|
|
translates upstream YAML parser structures into an independent internal document
|
|
model.
|
|
|
|
The selection of Cobra as an internal dependency is compatible with this
|
|
decision. Public API independence needs a genuine contract boundary, and
|
|
keeping backend structures out of shared internal representations also makes
|
|
future replacement easier.
|
|
|
|
## Decision
|
|
|
|
Use the module root `commandkit` as the sole initial supported public facade.
|
|
Define CommandKit's public named types there and expose only selected operations,
|
|
extension contracts, and documented methods. Handles keep implementation state in
|
|
private fields.
|
|
|
|
Implementation packages belong under Go's `internal/` boundary. Public code
|
|
forwards inward with explicit wrappers and conversions; internal production
|
|
packages do not import the root. Shared internal contracts live inside that
|
|
boundary. Additional public packages require a deliberate decision based on a
|
|
durable consumer responsibility.
|
|
|
|
Do not alias public types to internal or backend types, anonymously embed those
|
|
types to promote methods, or expose implementation bridge methods. Exported
|
|
fields, signatures, variables, callbacks, interface methods, constraints, and
|
|
method sets must not reveal internal or Cobra/pflag structures. Loosely typed
|
|
values and returned errors must not provide indirect backend-object access.
|
|
|
|
Standard contracts such as `context.Context` and `io.Writer` remain appropriate.
|
|
Public types from integration libraries, such as ConfigKit and PromptKit, may
|
|
be selected as deliberate dependencies. Application input types remain application-owned.
|
|
Go declarations and GoDoc own exact exported contracts.
|
|
|
|
Confine Cobra/pflag types and operations to an internal adapter. Registry logic,
|
|
standard handlers, and shared definitions use CommandKit-owned representations.
|
|
The adapter translates paths, descriptions, bindings, presence, callbacks, and
|
|
selected help/outcome semantics into backend machinery. Do not reproduce Cobra's
|
|
complete API through renamed wrappers.
|
|
|
|
Typed input factories and handlers can cross the facade through private callbacks
|
|
without internal packages importing public declarations. Preserve fresh input and
|
|
backend state per invocation. Introduce narrow internal interfaces where actual
|
|
engine/adapter interaction justifies them; exact package names and splits remain
|
|
implementation choices.
|
|
|
|
Translate backend-owned syntax failures into CommandKit diagnostics and outcomes.
|
|
Preserve useful public identity for application handler errors and cancellation
|
|
according to the invocation contract. Explicit conversions must preserve value
|
|
ownership, callback lifetime, presence, and supported error semantics.
|
|
|
|
## Alternatives considered
|
|
|
|
| Alternative | Analysis |
|
|
| --- | --- |
|
|
| Keep all machinery in the public root | Less forwarding, but harder consumer navigation and easier accidental public expansion. |
|
|
| Alias or embed implementation types | Less conversion code, but couples public type identity, fields, or method sets to internal choices. |
|
|
| Hide Cobra only behind the public facade | Hides types from applications but allows backend structures to spread through internal logic, complicating replacement. |
|
|
| Mirror Cobra's API with wrappers | Removes dependency names while preserving its conceptual coupling and compatibility surface. |
|
|
| Public packages for registry, bindings, backend, and other mechanisms | Adds supported imports and assembly contracts without a distinct consumer responsibility. |
|
|
| Selectable/pluggable backends immediately | Adds selection and extension machinery before a concrete need exists. |
|
|
|
|
## Consequences
|
|
|
|
Consumers depend on deliberate root contracts while internal algorithms,
|
|
collaborators, and package boundaries can evolve. Useful application registration
|
|
capabilities still need a proportionate API; minimizing accidental exports must
|
|
not recreate repetitive consumer infrastructure.
|
|
|
|
Explicit forwarding and conversion require maintenance. Type identity, ownership,
|
|
method sets, callback lifetime, and error semantics must be protected by evidence,
|
|
not inferred from package relocation.
|
|
|
|
Compiled public API checks protect aliases, fields, signatures, constraints, and
|
|
promoted methods. Import checks keep the backend in its adapter and internal
|
|
production code independent of root. External-package tests protect consumer
|
|
semantics through the real adapter. ConfigKit and ReleaseKit provide precedents
|
|
for these checks without requiring their full validation workflows.
|
|
|
|
Backend replacement can preserve the public Go API but must also preserve selected
|
|
CLI behavior or follow the release policy for intentional incompatibilities.
|
|
Backend defaults do not independently establish new CommandKit contracts.
|
|
|
|
The [architecture policy](../policy/architecture.md) owns normative boundaries.
|
|
[ADR 0001](0001-command-dispatch-and-inputs.md) records the dispatcher/input model,
|
|
and [ADR 0002](0002-cobra-backend.md) selects the initial backend.
|