Audit the public facade boundary
This commit is contained in:
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"schema_version": 2,
|
"schema_version": 2,
|
||||||
"commit": "550f92d7c6f197d7f4996919a418d9ca0174eb07",
|
"commit": "7f46c947dd56f437077df475ba5bba8fd864e2e1",
|
||||||
"indexed_at": "2026-09-12T16:21:16Z",
|
"indexed_at": "2026-09-12T16:26:42Z",
|
||||||
"project": "home-eric-Workspace-ConfigKit",
|
"project": "home-eric-Workspace-ConfigKit",
|
||||||
"nodes": 1625,
|
"nodes": 1625,
|
||||||
"edges": 7835,
|
"edges": 7824,
|
||||||
"original_size": 8126464,
|
"original_size": 8126464,
|
||||||
"compressed_size": 1330563,
|
"compressed_size": 1329933,
|
||||||
"compression_level": 3
|
"compression_level": 3
|
||||||
}
|
}
|
||||||
Binary file not shown.
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted. This records a design decision, not implementation completion.
|
Accepted and implemented on 2026-09-12.
|
||||||
|
|
||||||
## Date
|
## Date
|
||||||
|
|
||||||
@@ -10,32 +10,31 @@ Accepted. This records a design decision, not implementation completion.
|
|||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
The root `configkit` package currently combines the supported consumer API with
|
The root `configkit` package formerly combined the supported consumer API with
|
||||||
the loading, decoding, inspection, and rendering machinery. That makes ordinary
|
loading, decoding, inspection, and rendering machinery. That made ordinary
|
||||||
consumer navigation harder and risks exposing implementation details through
|
consumer navigation harder and risked exposing implementation details through
|
||||||
future public declarations. Moving files alone would not preserve the identity,
|
future public declarations. Moving files alone would not preserve the identity,
|
||||||
method sets, callbacks, ownership rules, or reflection-sensitive behavior of
|
method sets, callbacks, ownership rules, or reflection-sensitive behavior of the
|
||||||
the root's public types.
|
root's public types.
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
Keep `configkit` as the sole supported consumer import path and keep all public
|
Keep `configkit` as the sole supported consumer import path and keep all public
|
||||||
named types defined there. When lifecycle machinery is extracted, it will live
|
named types defined there. Lifecycle machinery lives in a cohesive
|
||||||
in a cohesive `internal/engine`; the root facade may hold private engine state
|
`internal/engine`; the root facade holds private engine state and forwards
|
||||||
and forward deliberately selected methods. The dependency direction is root to
|
deliberately selected methods. The dependency direction is root to engine and
|
||||||
engine and its supporting internal packages. Internal production packages do
|
its supporting internal packages. Internal production packages do not import the
|
||||||
not import the root package.
|
root package.
|
||||||
|
|
||||||
The facade will use root-owned wrappers and explicit private conversions. It
|
The facade will use root-owned wrappers and explicit private conversions. It
|
||||||
will not use public aliases to engine types, anonymous embedding that promotes
|
will not use public aliases to engine types, anonymous embedding that promotes
|
||||||
engine methods, public engine types in signatures or fields, or public bridge
|
engine methods, public engine types in signatures or fields, or public bridge
|
||||||
methods. Existing document and YAML boundaries remain intact.
|
methods. Existing document and YAML boundaries remain intact.
|
||||||
|
|
||||||
The migration may use a temporary internal candidate facade while the unchanged
|
The migration used a temporary internal candidate facade while the unchanged root
|
||||||
root remains the only production entry point. That duplication is a bounded
|
remained the only production entry point. That duplication was a bounded
|
||||||
relocation mechanism, not a second supported package, feature path, or runtime
|
relocation mechanism, not a second supported package, feature path, or runtime
|
||||||
selection mechanism. The final cutover removes the temporary candidate and old
|
selection mechanism; the final cutover removed it and the old root implementation.
|
||||||
root implementation together.
|
|
||||||
|
|
||||||
The [architecture policy](../policy/architecture.md#public-api-and-dependency-direction)
|
The [architecture policy](../policy/architecture.md#public-api-and-dependency-direction)
|
||||||
owns the binding boundary. The [public-facade roadmap](../roadmap/public.md)
|
owns the binding boundary. The [public-facade roadmap](../roadmap/public.md)
|
||||||
|
|||||||
@@ -98,15 +98,15 @@ code changes. Use the component overview to find the corresponding implementatio
|
|||||||
|
|
||||||
| Task | Behavioral test owners | Focused command |
|
| Task | Behavioral test owners | Focused command |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| YAML syntax, diagnostics, parser limits | [Adapter tests](../internal/yaml/parse_test.go), [source translation](../internal/engine/parse_test.go) | `go test ./internal/yaml` and `go test . -run '^TestParseSource'` |
|
| YAML syntax, diagnostics, parser limits | [Adapter tests](../internal/yaml/parse_test.go), [source translation](../internal/engine/parse_test.go) | `go test ./internal/yaml` and `go test ./internal/engine -run '^TestParseSource'` |
|
||||||
| File/reader access, imports, globs, containment | [Sources](../internal/engine/source_test.go), [FIFO](../internal/engine/source_fifo_test.go), [imports](../internal/engine/imports_test.go), [globs](../internal/engine/glob_test.go) | `go test . -run 'Source\|Import\|Glob\|Filesystem\|Nonregular\|RegularFile'` |
|
| File/reader access, imports, globs, containment | [Sources](../internal/engine/source_test.go), [FIFO](../internal/engine/source_fifo_test.go), [imports](../internal/engine/imports_test.go), [globs](../internal/engine/glob_test.go) | `go test ./internal/engine -run 'Source\|Import\|Glob\|Filesystem\|Nonregular\|RegularFile'` |
|
||||||
| Composition, profiles, CLI precedence | [Composition](../internal/engine/compose_test.go), [overlay](../internal/engine/overlay_test.go), [profiles](../internal/engine/profile_test.go), [overrides](../internal/engine/override_test.go) | `go test . -run 'Compose\|Overlay\|Profile\|Override'` |
|
| Composition, profiles, CLI precedence | [Composition](../internal/engine/compose_test.go), [overlay](../internal/engine/overlay_test.go), [profiles](../internal/engine/profile_test.go), [overrides](../internal/engine/override_test.go) | `go test ./internal/engine -run 'Compose\|Overlay\|Profile\|Override'` |
|
||||||
| Schema, defaults, projection, cloning | [Schema](../internal/engine/schema_test.go), [decoding](../internal/engine/decode_test.go), [projection](../internal/engine/projection_test.go) | `go test . -run 'Schema\|Decode\|Projection\|Clone\|Inline\|CompareDocument'` |
|
| Schema, defaults, projection, cloning | [Schema](../internal/engine/schema_test.go), [decoding](../internal/engine/decode_test.go), [projection](../internal/engine/projection_test.go) | `go test ./internal/engine -run 'Schema\|Decode\|Projection\|Clone\|Inline\|CompareDocument'` |
|
||||||
| Lifecycle, resolution, validation, callback ownership | [Loading](../internal/engine/load_test.go), [resolution](../internal/engine/resolution_test.go), [validation](../internal/engine/validation_test.go), [ownership](../internal/engine/callback_ownership_test.go), [independence](../internal/engine/independence_test.go) | `go test . -run 'Load\|Resolution\|Validation\|Validator\|Hook\|Callback\|Parallel'` |
|
| Lifecycle, resolution, validation, callback ownership | [Loading](../internal/engine/load_test.go), [resolution](../internal/engine/resolution_test.go), [validation](../internal/engine/validation_test.go), [ownership](../internal/engine/callback_ownership_test.go), [independence](../internal/engine/independence_test.go) | `go test ./internal/engine -run 'Load\|Resolution\|Validation\|Validator\|Hook\|Callback\|Parallel'` |
|
||||||
| Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test . -run 'Path\|Metadata\|Provenance\|Queries'` |
|
| Paths, metadata, provenance | [Paths](../internal/engine/path_test.go), [metadata](../internal/engine/metadata_test.go), [provenance](../internal/engine/provenance_test.go) | `go test ./internal/engine -run 'Path\|Metadata\|Provenance\|Queries'` |
|
||||||
| Secrets, bootstrap, redaction, explanation | [References](../internal/engine/secret_reference_test.go), [bootstrap](../internal/engine/secrets_test.go), [rendering](../internal/engine/render_test.go), [explanation](../internal/engine/explain_test.go) | `go test . -run 'Secret\|Bootstrap\|Effective\|Explain\|Explanation\|History\|Sensitivity'` |
|
| Secrets, bootstrap, redaction, explanation | [References](../internal/engine/secret_reference_test.go), [bootstrap](../internal/engine/secrets_test.go), [rendering](../internal/engine/render_test.go), [explanation](../internal/engine/explain_test.go) | `go test ./internal/engine -run 'Secret\|Bootstrap\|Effective\|Explain\|Explanation\|History\|Sensitivity'` |
|
||||||
| Consumer examples and migration fixtures | [Consumer tests](../examples/consumers/consumers_test.go), [API examples](../example_test.go) | `go test ./examples/consumers` and `go test . -run '^Example'` |
|
| Consumer examples and migration fixtures | [Consumer tests](../examples/consumers/consumers_test.go), [API examples](../example_test.go) | `go test ./examples/consumers` and `go test . -run '^Example'` |
|
||||||
| Documentation links and dependency isolation | [Link checks](../documentation_test.go), [import boundary](../import_boundary_test.go) | `go test . -run '^(TestLocalDocumentationLinks\|TestDependencyImportBoundary)$'` |
|
| Documentation links and dependency isolation | [Link checks](../documentation_test.go), [import boundary](../import_boundary_test.go) | `go test . -run 'TestLocalDocumentationLinks|TestDependencyImportBoundary|TestPublicFacadeBoundary'` |
|
||||||
| Check-command behavior | [Shell regression](../release_check_test.go) | `go test . -run '^TestCheckScripts'` |
|
| Check-command behavior | [Shell regression](../release_check_test.go) | `go test . -run '^TestCheckScripts'` |
|
||||||
|
|
||||||
## Fuzz validation
|
## Fuzz validation
|
||||||
@@ -115,7 +115,20 @@ Run the bounded fuzz targets independently from the repository root. These exact
|
|||||||
commands keep each campaign short and constrain concurrent workers:
|
commands keep each campaign short and constrain concurrent workers:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
go test . -run='^$' -fuzz='^FuzzParsePath$' -fuzztime=30s -parallel=2
|
go test ./internal/engine -run='^$' -fuzz='^FuzzParsePath$' -fuzztime=30s -parallel=2
|
||||||
go test ./internal/yaml -run='^$' -fuzz='^FuzzParseDocument$' -fuzztime=30s -parallel=2
|
go test ./internal/yaml -run='^$' -fuzz='^FuzzParseDocument$' -fuzztime=30s -parallel=2
|
||||||
go test . -run='^$' -fuzz='^FuzzCompositionAndOverlay$' -fuzztime=30s -parallel=2
|
go test ./internal/engine -run='^$' -fuzz='^FuzzCompositionAndOverlay$' -fuzztime=30s -parallel=2
|
||||||
|
```
|
||||||
|
` |
|
||||||
|
| Check-command behavior | [Shell regression](../release_check_test.go) | `go test . -run '^TestCheckScripts'` |
|
||||||
|
|
||||||
|
## Fuzz validation
|
||||||
|
|
||||||
|
Run the bounded fuzz targets independently from the repository root. These exact
|
||||||
|
commands keep each campaign short and constrain concurrent workers:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go test ./internal/engine -run='^$' -fuzz='^FuzzParsePath$' -fuzztime=30s -parallel=2
|
||||||
|
go test ./internal/yaml -run='^$' -fuzz='^FuzzParseDocument$' -fuzztime=30s -parallel=2
|
||||||
|
go test ./internal/engine -run='^$' -fuzz='^FuzzCompositionAndOverlay$' -fuzztime=30s -parallel=2
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,94 +1,39 @@
|
|||||||
# Internal Component Overview
|
# Internal Component Overview
|
||||||
|
|
||||||
ConfigKit exposes the root `configkit` package and keeps its document and YAML
|
ConfigKit exposes one supported root `configkit` package. The root owns public
|
||||||
representations private. The implemented components and their owners are:
|
named types, declaration comments, constructors, and explicit conversion/wrapper
|
||||||
|
code. It delegates loading and frozen inspection to `internal/engine` through
|
||||||
|
private state; public signatures and fields do not expose engine types.
|
||||||
|
|
||||||
- a temporary `internal/engine` relocation candidate containing a mechanical
|
Implemented component owners:
|
||||||
copy of the root loading, decoding, resolution, provenance, inspection, and
|
|
||||||
rendering mechanisms with their behavior tests; it imports no root code and
|
|
||||||
is not yet used by the root package or consumers;
|
|
||||||
- immutable semantic `Path` construction and strict JSON Pointer parsing;
|
|
||||||
- detached effective presence/origin and chronological history queries in
|
|
||||||
`metadata.go`, with provenance and diagnostic record types;
|
|
||||||
- canonical, control-safe diagnostic rendering and structured `LoadError` access;
|
|
||||||
- opaque file, byte, reader, and no-input source construction in `source.go`,
|
|
||||||
with shared regular-file checks and bounded read mechanics in `source_file.go`;
|
|
||||||
- a parser-independent ordered tree in `internal/document`, with root-private
|
|
||||||
semantic lookup, indexing, and value traversal in `document_walk.go`;
|
|
||||||
- the sole upstream YAML integration in `internal/yaml`, which translates validated
|
|
||||||
input into that owned tree and exposes no parser types outside its boundary;
|
|
||||||
`parse.go` owns shared translation into detached, ordered public diagnostics;
|
|
||||||
- a private strict composer in `compose.go` that joins only disjoint document-tree
|
|
||||||
definitions, retains ordered claims and composite mapping origins, reports every
|
|
||||||
conflicting location, and never aliases an input tree;
|
|
||||||
- fixed selected-profile loading in `profile.go` and recursive, non-aliasing overlay
|
|
||||||
with displacement records in `overlay.go`; overlay records retain origins,
|
|
||||||
while value explanation uses separate history applications;
|
|
||||||
- CLI override validation/preparation in `override.go`, parser-neutral values in
|
|
||||||
`override_value.go`, and synthetic-source application in `override_apply.go`,
|
|
||||||
sharing overlay, source, byte, node, and depth policies;
|
|
||||||
- a per-operation private reflection descriptor graph in `schema.go` for supported
|
|
||||||
types, YAML field binding, inlining, secret tags, recursion, and semantic paths;
|
|
||||||
- descriptor-driven canonical projection in `projection.go`, detached typed
|
|
||||||
cloning with explicit secret-state modes in `clone.go`, shared reflection and
|
|
||||||
cycle/limit state in `value_operation.go`, and configuration-value comparison
|
|
||||||
in `document_compare.go`; validation separately audits sensitivity and secrets;
|
|
||||||
- strict schema-driven decoding over detached defaults in `decode.go`, including
|
|
||||||
mapping/sequence semantics, null and numeric policy, safe codec errors, presence,
|
|
||||||
baseline/final trees, and initial default/input origins;
|
|
||||||
- public lifecycle coordination in `load.go`, option selection in `options.go`,
|
|
||||||
input assembly in `load_input.go`, typed phases/publication in `load_result.go`,
|
|
||||||
bounded canonical import DFS in `imports.go`, filesystem context/containment in
|
|
||||||
`filesystem.go`, directive parsing/restrictions in `directives.go`,
|
|
||||||
pattern-directed glob expansion in `glob.go`, schema-version checking and report
|
|
||||||
contexts in `validation.go`, and layered metadata/history assembly in
|
|
||||||
`load_metadata.go`;
|
|
||||||
- recorded application defaulting and normalization in `resolution.go`, including
|
|
||||||
callback-scoped fixed entry metadata/source queries, explicitness queries, helper
|
|
||||||
verification, structural mutation audit, coalesced intent, private before/after
|
|
||||||
snapshots, and resolution metadata integration;
|
|
||||||
- post-resolution schema-aware report collection and validator mutation detection in
|
|
||||||
`validation.go`, completing the public callback lifecycle after composed input;
|
|
||||||
- opaque typed environment references and per-load name resolution in
|
|
||||||
`secret_reference.go`, including credential-free projection/cloning and private
|
|
||||||
validator-state auditing;
|
|
||||||
- explicit bounded startup environment bootstrap and safe partial reporting in
|
|
||||||
`secrets.go`;
|
|
||||||
- fixed-point tag/predicate/dependency sensitivity classification in `secret.go`;
|
|
||||||
- canonical effective output in `render.go`, using frozen-snapshot redaction
|
|
||||||
shared with explanation in `snapshot.go`;
|
|
||||||
- detached public explanation records and coordination in `explain.go`, immutable
|
|
||||||
input applications/replay and retained-node budgeting in `history.go`, shared
|
|
||||||
safe snapshot projection in `snapshot.go`, and deterministic human output in
|
|
||||||
`explain_render.go`; the only upstream dumper integration remains isolated in
|
|
||||||
`internal/yaml/write.go`;
|
|
||||||
- a separate maintained `examples/consumers` package with four sanitized consumer
|
|
||||||
shapes, fixtures, domain validators, and runtime-boundary functions; and
|
|
||||||
- an everyday single-toolchain check in `scripts/check.sh` and a stricter
|
|
||||||
source-candidate checker in `scripts/release-check.sh`, with local
|
|
||||||
documentation-link and public import-boundary gates owned by root-package tests.
|
|
||||||
|
|
||||||
Dependency direction remains inward: the public lifecycle coordinates private
|
- `internal/engine` owns source acquisition, composition, schema inspection,
|
||||||
source, document, schema, and inspection helpers. Semantic paths stay in the root
|
decoding, lifecycle coordination, callback auditing, provenance/history,
|
||||||
package; `internal/document` owns only representation and cloning and never imports
|
secret resolution and bootstrap, and safe inspection rendering. It never
|
||||||
the root package. Only `internal/yaml` imports the upstream YAML package. Maintained
|
imports the root package.
|
||||||
consumer examples use the public package and standard library.
|
- Root `Source`, `Path`, `Secret`, records, lifecycle callbacks, results, and
|
||||||
|
bootstrap reports are root-defined wrappers or records. Callback wrappers pass
|
||||||
|
only live engine context during a callback; `Result.Config` is detached and its
|
||||||
|
private inspection remains frozen independently.
|
||||||
|
- Root conversion files explicitly cross the path, record, callback, secret, and
|
||||||
|
error boundaries. There are no public aliases, anonymous engine embeddings, or
|
||||||
|
public engine bridge methods.
|
||||||
|
- `internal/document` owns the parser-independent ordered tree and cloning.
|
||||||
|
`internal/yaml` is the sole upstream YAML adapter and parser/dumper boundary.
|
||||||
|
- Engine mechanism tests own source, composition, decoding, callback, provenance,
|
||||||
|
secret, history, rendering, and bootstrap behavior. Root tests own public API
|
||||||
|
compatibility, facade integration, documentation links, and dependency/public
|
||||||
|
boundary checks. Consumer examples remain under `examples/consumers`.
|
||||||
|
|
||||||
The [configuration contract](../config.md) owns implemented input, schema, decoding,
|
Dependency direction is root to engine to document/YAML support. The root may
|
||||||
loading lifecycle, path imports, composition, profile/CLI overlay, version checking,
|
import only `internal/engine` among project internal production packages; engine
|
||||||
recorded resolution, typed secret references/bootstrap, semantic validation,
|
and its supporting packages do not import root. The root boundary test enforces
|
||||||
metadata, secret classification, redacted effective rendering, historical
|
these constraints and rejects exported aliases, fields, or signatures involving
|
||||||
explanation, and ownership behavior.
|
internal types.
|
||||||
The [consumer adoption guide](../consumers/README.md) owns current application-fit
|
|
||||||
and migration guidance.
|
|
||||||
|
|
||||||
The source-extraction inventory records the application foundations used by the
|
The [configuration contract](../config.md) owns implemented YAML and lifecycle
|
||||||
implementation:
|
semantics. The [consumer adoption guide](../consumers/README.md) owns application
|
||||||
|
guidance. The [architecture policy](../policy/architecture.md) owns contributor
|
||||||
- [Extraction inventory](extraction.md)
|
boundaries, and [ADR 0006](../adr/0006-public-facade.md) records the facade
|
||||||
|
decision. See the [development guide](../development.md) for validation commands
|
||||||
The [architecture policy](../policy/architecture.md) owns binding boundaries and
|
and [release procedure](../release.md) for source-candidate gates.
|
||||||
invariants. The [deferred scope](../roadmap/deferred.md) records deliberate
|
|
||||||
exclusions. See the
|
|
||||||
[development guide](../development.md) for contributor orientation and validation
|
|
||||||
and the [release procedure](../release.md) for source-candidate gates.
|
|
||||||
|
|||||||
@@ -83,10 +83,10 @@ types or acquire internal methods through anonymous embedding. Root production
|
|||||||
code may depend inward on a cohesive engine and its supporting internal packages,
|
code may depend inward on a cohesive engine and its supporting internal packages,
|
||||||
but internal production packages must not import the root. Explicit forwarding
|
but internal production packages must not import the root. Explicit forwarding
|
||||||
and local conversion are acceptable where they preserve type identity,
|
and local conversion are acceptable where they preserve type identity,
|
||||||
ownership, callback lifetime, and inspection behavior. The accepted
|
ownership, callback lifetime, and inspection behavior. The accepted root-wrapper
|
||||||
root-wrapper boundary and its migration rationale are recorded in
|
boundary and its migration rationale are recorded in
|
||||||
[ADR 0006](../adr/0006-public-facade.md); its implementation remains tracked by
|
[ADR 0006](../adr/0006-public-facade.md); completed implementation evidence is
|
||||||
the public-facade roadmap.
|
recorded in the public-facade roadmap.
|
||||||
|
|
||||||
Separate input access, document processing, composition, decoding, metadata, and
|
Separate input access, document processing, composition, decoding, metadata, and
|
||||||
rendering responsibilities without prescribing an interface or package for each.
|
rendering responsibilities without prescribing an interface or package for each.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Public facade: implementation plan
|
# Public facade: implementation plan
|
||||||
|
|
||||||
Status: not started. Baseline: `28e7fc5f690b36c60e4d03586afcff00fb1dee90`.
|
Status: complete (2026-09-12). Baseline: `28e7fc5f690b36c60e4d03586afcff00fb1dee90`.
|
||||||
The [feature roadmap](public.md) owns the target architecture and user intent.
|
The [feature roadmap](public.md) owns the implemented architecture and user intent.
|
||||||
This plan owns implementation decisions, ordered work, validation, and handoffs.
|
This completed plan records implementation decisions, ordered work, validation, and handoffs.
|
||||||
Each numbered stage is sized for one gpt-5.6-terra coding-agent assignment;
|
Each numbered stage is sized for one gpt-5.6-terra coding-agent assignment;
|
||||||
no stage requires subagents or changes to downstream repositories.
|
no stage requires subagents or changes to downstream repositories.
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ boundary and brings documentation in line with the completed architecture.
|
|||||||
|
|
||||||
## Stage 9 — Audit the public boundary and finalize documentation
|
## Stage 9 — Audit the public boundary and finalize documentation
|
||||||
|
|
||||||
Status: not started.
|
Status: complete (2026-09-12).
|
||||||
|
|
||||||
1. Compare the final complete exported API against the baseline. Audit public
|
1. Compare the final complete exported API against the baseline. Audit public
|
||||||
signatures, fields, aliases, embedded types, method sets, and error returns
|
signatures, fields, aliases, embedded types, method sets, and error returns
|
||||||
@@ -538,6 +538,29 @@ supported baseline and newer Go toolchains recorded in the release procedure.
|
|||||||
Record actual versions/platforms; do not treat Linux tests or cross-builds as
|
Record actual versions/platforms; do not treat Linux tests or cross-builds as
|
||||||
native macOS release evidence. Do not publish or select a version in this stage.
|
native macOS release evidence. Do not publish or select a version in this stage.
|
||||||
|
|
||||||
|
### Completion
|
||||||
|
|
||||||
|
Audited the final root facade against the established public surface and added a
|
||||||
|
root AST boundary check for exported aliases, fields, and signatures involving
|
||||||
|
internal types. The dependency boundary check now also rejects root production
|
||||||
|
imports that bypass the engine and internal production imports of the root.
|
||||||
|
The completed root retains only facade wrappers, records, conversions, and
|
||||||
|
entry-point delegation; engine mechanism tests own lifecycle behavior.
|
||||||
|
|
||||||
|
Updated the architecture policy, ADR status, component overview, development
|
||||||
|
test/fuzz navigation, and public-facade roadmap to describe the implemented
|
||||||
|
layout. The roadmap is now a completed implementation record rather than a
|
||||||
|
future plan. Root facade integration and engine tests cover source ownership,
|
||||||
|
exact-type secret binding, callback activity/audit, structured errors, detached
|
||||||
|
configuration versus frozen inspection, and resolution entry snapshots.
|
||||||
|
|
||||||
|
Linux validation passed with Go 1.25.5 and Go 1.26.7 through
|
||||||
|
`CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5 CONFIGKIT_NEWER_TOOLCHAIN=go1.26.7 ./scripts/release-check.sh`.
|
||||||
|
It also passed the everyday check, full race suite, consumer examples, and local
|
||||||
|
documentation/import/public-boundary tests. This is not native macOS evidence
|
||||||
|
and no release version, tag, or publication was selected. There were no
|
||||||
|
deviations or remaining risks.
|
||||||
|
|
||||||
## Open Questions
|
## Open Questions
|
||||||
|
|
||||||
None requiring a user decision were identified. Wrappers, public type ownership,
|
None requiring a user decision were identified. Wrappers, public type ownership,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Public facade and internal implementation
|
# Public facade and internal implementation
|
||||||
|
|
||||||
Status: agreed direction; not implemented. This roadmap defines the desired end
|
Status: implemented on 2026-09-12. This roadmap records the intended architecture
|
||||||
state and user intent. The [implementation plan](implementation.md) owns the
|
and user intent; the completed [implementation plan](implementation.md) records
|
||||||
ordered work, concrete adapter design, validation commands, and handoffs.
|
the ordered work, validation, and handoffs.
|
||||||
|
|
||||||
## Intent
|
## Intent
|
||||||
|
|
||||||
@@ -31,13 +31,13 @@ design. Existing [configuration contracts](../config.md) and exported Go
|
|||||||
declaration comments remain authoritative. The user-approved wrapper boundary is
|
declaration comments remain authoritative. The user-approved wrapper boundary is
|
||||||
a contributor design choice, not a strengthening of consumer behavior contracts.
|
a contributor design choice, not a strengthening of consumer behavior contracts.
|
||||||
|
|
||||||
## Current architecture
|
## Implemented architecture
|
||||||
|
|
||||||
The root currently owns both public declarations and most private implementation:
|
The root owns public declarations, records, wrappers, and deliberate forwarding.
|
||||||
source acquisition, imports, composition and overlays, reflection descriptors,
|
`internal/engine` owns source acquisition, imports, composition and overlays,
|
||||||
decoding, resolution and validation coordination, provenance, history, secret
|
reflection descriptors, decoding, resolution and validation coordination,
|
||||||
handling, and rendering. The [component overview](../internal/overview.md) owns
|
provenance, history, secret handling, and rendering. The
|
||||||
the detailed inventory.
|
[component overview](../internal/overview.md) owns the detailed inventory.
|
||||||
|
|
||||||
Two established internal boundaries already exist: `internal/document` owns the
|
Two established internal boundaries already exist: `internal/document` owns the
|
||||||
parser-independent tree, and `internal/yaml` owns upstream YAML integration.
|
parser-independent tree, and `internal/yaml` owns upstream YAML integration.
|
||||||
@@ -49,7 +49,7 @@ callback state; `Path` holds semantic segments; and `Secret` holds private
|
|||||||
reference and resolved-value state. Moving implementation requires deliberate
|
reference and resolved-value state. Moving implementation requires deliberate
|
||||||
type ownership, not just file relocation.
|
type ownership, not just file relocation.
|
||||||
|
|
||||||
## Target architecture
|
## Architecture
|
||||||
|
|
||||||
### Public root package
|
### Public root package
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ need to change an established contract requires a separate explicit decision.
|
|||||||
Version selection and publication remain governed by the
|
Version selection and publication remain governed by the
|
||||||
[release policy](../policy/release.md), not by this roadmap.
|
[release policy](../policy/release.md), not by this roadmap.
|
||||||
|
|
||||||
## Completion criteria
|
## Completion evidence
|
||||||
|
|
||||||
Completion means the root is a legible public surface, substantive plumbing is
|
Completion means the root is a legible public surface, substantive plumbing is
|
||||||
internally owned, consumers require no migration, and representative
|
internally owned, consumers require no migration, and representative
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package configkit
|
package configkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
@@ -8,6 +9,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDependencyImportBoundary(t *testing.T) {
|
func TestDependencyImportBoundary(t *testing.T) {
|
||||||
@@ -41,6 +43,15 @@ func TestDependencyImportBoundary(t *testing.T) {
|
|||||||
if strings.HasPrefix(importPath, "github.com/google/go-cmp/") && !strings.HasSuffix(path, "_test.go") {
|
if strings.HasPrefix(importPath, "github.com/google/go-cmp/") && !strings.HasSuffix(path, "_test.go") {
|
||||||
t.Errorf("%s imports test-only dependency %s", path, importPath)
|
t.Errorf("%s imports test-only dependency %s", path, importPath)
|
||||||
}
|
}
|
||||||
|
production := !strings.HasSuffix(path, "_test.go")
|
||||||
|
rootProduction := production && filepath.Dir(path) == "."
|
||||||
|
internalProduction := production && strings.HasPrefix(filepath.ToSlash(path), "internal/")
|
||||||
|
if rootProduction && strings.HasPrefix(importPath, "gitea.maximumdirect.net/eric/ConfigKit/internal/") && importPath != "gitea.maximumdirect.net/eric/ConfigKit/internal/engine" {
|
||||||
|
t.Errorf("%s bypasses the engine boundary through %s", path, importPath)
|
||||||
|
}
|
||||||
|
if internalProduction && importPath == "gitea.maximumdirect.net/eric/ConfigKit" {
|
||||||
|
t.Errorf("%s imports the public root package", path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -48,3 +59,92 @@ func TestDependencyImportBoundary(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPublicFacadeBoundary(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
entries, err := filepath.Glob("*.go")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, path := range entries {
|
||||||
|
if strings.HasSuffix(path, "_test.go") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
imports := importedPaths(file)
|
||||||
|
for _, declaration := range file.Decls {
|
||||||
|
switch declaration := declaration.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
if exportedIdentifier(declaration.Name.Name) && usesInternalType(declaration.Type, imports) {
|
||||||
|
t.Errorf("%s exposes an internal type in its signature", declaration.Name)
|
||||||
|
}
|
||||||
|
case *ast.GenDecl:
|
||||||
|
for _, specification := range declaration.Specs {
|
||||||
|
typeSpec, ok := specification.(*ast.TypeSpec)
|
||||||
|
if !ok || !exportedIdentifier(typeSpec.Name.Name) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if typeSpec.Assign.IsValid() {
|
||||||
|
t.Errorf("%s is an exported type alias", typeSpec.Name)
|
||||||
|
}
|
||||||
|
structure, ok := typeSpec.Type.(*ast.StructType)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, field := range structure.Fields.List {
|
||||||
|
exposed := len(field.Names) == 0
|
||||||
|
for _, name := range field.Names {
|
||||||
|
exposed = exposed || exportedIdentifier(name.Name)
|
||||||
|
}
|
||||||
|
if exposed && usesInternalType(field.Type, imports) {
|
||||||
|
t.Errorf("%s exposes an internal type through a field", typeSpec.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func importedPaths(file *ast.File) map[string]string {
|
||||||
|
paths := make(map[string]string, len(file.Imports))
|
||||||
|
for _, imported := range file.Imports {
|
||||||
|
path, err := strconv.Unquote(imported.Path.Value)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := filepath.Base(path)
|
||||||
|
if imported.Name != nil {
|
||||||
|
name = imported.Name.Name
|
||||||
|
}
|
||||||
|
paths[name] = path
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
func usesInternalType(expression ast.Expr, imports map[string]string) bool {
|
||||||
|
internal := false
|
||||||
|
ast.Inspect(expression, func(node ast.Node) bool {
|
||||||
|
selector, ok := node.(*ast.SelectorExpr)
|
||||||
|
if !ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
packageName, ok := selector.X.(*ast.Ident)
|
||||||
|
if ok && strings.Contains(imports[packageName.Name], "/internal/") {
|
||||||
|
internal = true
|
||||||
|
}
|
||||||
|
return !internal
|
||||||
|
})
|
||||||
|
return internal
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportedIdentifier(name string) bool {
|
||||||
|
for _, character := range name {
|
||||||
|
return unicode.IsUpper(character)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user