Add explicit path import traversal

This commit is contained in:
2026-09-06 20:41:40 +00:00
parent 3257b2124c
commit 9f04c09be3
15 changed files with 1233 additions and 118 deletions

View File

@@ -20,10 +20,11 @@ The zero `Source` is invalid. Constructor argument failures are retained inside
opaque source and become safe structured diagnostics when the source is consumed.
File sources can observe file changes between consumptions; byte sources cannot.
Each source is limited to 8 MiB. A load will be limited to 64 MiB across all source
and override input when those layers are implemented. Reads request at most the
remaining allowance plus one byte so excess data is rejected rather than silently
truncated. Data returned together with a read error is not accepted as partial input.
Each source is limited to 8 MiB. An imported load is limited to 64 MiB across all
accepted sources, 1,024 accepted sources, 64 active import edges, and 1,000,000
input nodes in aggregate. Reads request at most the remaining allowance plus one byte
so excess data is rejected rather than silently truncated. Data returned together
with a read error is not accepted as partial input.
## Single-source loading
@@ -36,21 +37,22 @@ schema-version checking. Its result is detached before input is applied.
The current lifecycle is:
```text
validate options → parse one source → reject reserved directives → build schema
→ check the optional application version → create and clone defaults
validate options → parse the selected source → traverse/compose enabled imports
→ build schema → check the optional application version → create and clone defaults
→ strictly decode → recorded application resolution → semantic validation
→ snapshot configuration and metadata
```
`File`, `Bytes`, and `Reader` each contribute one base source with the deterministic
ID `source-1`. `NoSource` skips parsing, supplies an absent version to a configured
version checker, contributes no source, and continues through defaults, decode, and
projection. A missing file remains an error; it is never interpreted as no input.
`File`, `Bytes`, and `Reader` each contribute the first base source with deterministic
ID `source-1`; enabled imports continue in depth-first order as `source-2` onward.
`NoSource` skips parsing, supplies an absent version to a configured version checker,
contributes no source, and continues through defaults, decode, and projection. A
missing file remains an error; it is never interpreted as no input.
The current `Options` boundary accepts a defaults factory, `VersionRule`, resolver,
and semantic validator. Imports, active profiles, filesystem context, overrides,
and dynamic secret classification return an explicit
`options.unavailable` error because their lifecycle stages are not implemented.
semantic validator, explicit path imports, and import filesystem context. Active
profiles, standalone filesystem context, overrides, and dynamic secret classification
return `options.unavailable` because their lifecycle stages are not implemented.
The reserved profile spelling `default` selects no active profile. `NoSource`
additionally rejects imports, active profiles, and filesystem context as invalid
combinations. Input containing a reserved `_configkit` mapping key is rejected while
@@ -152,10 +154,8 @@ live services as a substitute for configuration validation.
## Strict document composition
ConfigKit's private document layer can strictly compose parsed base documents. The
operation is available for file-graph integration but is not yet reachable through
the public single-source `Load` lifecycle. Application defaults do not participate
in this operation.
ConfigKit strictly composes parsed base documents when `Options.Imports` is true.
Application defaults do not participate in this operation.
| Existing node | Incoming node | Result |
| --- | --- | --- |
@@ -180,6 +180,44 @@ traversal order. Traversal order never authorizes replacement. Composition clone
the resulting tree and its ordered claim records rather than mutating or aliasing an
input document.
## Explicit path imports
Imports are opt-in. With `Options.Imports` enabled, a document may reserve its root
`_configkit` mapping for an `imports` sequence. ConfigKit removes that mapping before
application decoding. Each sequence item is a mapping with exactly one nonempty
string `path` or `glob` plus an optional boolean `optional`; all other shapes and
fields are `import.invalid`. `glob` is recognized but temporarily returns
`import.unsupported` until bounded glob expansion is implemented. A `_configkit`
key below the root is invalid. With imports disabled, any occurrence remains
`import.disabled`.
Path declarations use slash-separated relative paths. Absolute paths and network
URLs are invalid. Each path resolves from its declaring file's canonical directory,
and files are traversed depth-first in declaration order: a file contributes its own
body before its imports. Order stabilizes source records and diagnostics but never
permits replacement. Required missing paths fail; `optional: true` suppresses only
target nonexistence, not invalid paths, containment failures, directories,
permissions, parse errors, or composition errors.
The configuration root defaults to a selected file's canonical directory. A file
source may supply `FilesystemContext.Root` to widen that root but may not supply
`BaseDir`. Byte and reader sources must supply both `BaseDir` and `Root` as absolute
existing directories when imports are enabled; their diagnostic labels, including
URI-looking labels, never imply filesystem paths. The base and selected file must be
inside the canonical root. ConfigKit canonicalizes symlinks, checks containment with
relative paths, and opens the canonical root-relative target through a library-owned
root handle. Contained relative or absolute symlink targets are accepted; escapes
are rejected. Canonical paths define identity, so hard links at distinct paths remain
distinct sources.
An active-stack repeat is `import.cycle` and reports the complete cycle's import
sites. A previously visited canonical path is `import.repeated` and reports both
inclusion routes, including diamonds and aliases. Each accepted file is read once.
Imported fragments may contain their own root directives, but may not claim the
application's schema-version path or replace one of its ancestors; the version must
come from the selected source. No directory is scanned implicitly, including one
named `conf.d`, and ConfigKit performs no remote fetching.
## YAML documents
The private YAML boundary currently accepts exactly one nonempty document whose root