Settle import defaults, discovery boundaries, and inspection decisions

This commit is contained in:
2026-10-19 01:54:01 +00:00
parent 10c14aa794
commit a7ca2c59a2

View File

@@ -87,7 +87,7 @@ The fields have distinct jobs:
| `path` | Import one named file, relative to the declaring file |
| `dir` | Search a directory, relative to the declaring file |
| `pattern` | Filter candidate files by filename within the searched directories |
| `recursive` | Include descendant directories when true |
| `recursive` | Default false; include descendant directories when true |
| `depth` | Bound directory-search depth; 1 means direct files, 0 or omission means unlimited when recursive |
| `follow` | Follow symlinks by default; false disables following within the discovery scope defined below |
| `hidden` | Include hidden entries by default; false excludes hidden files and prunes hidden directories within the discovery scope defined below |
@@ -118,10 +118,10 @@ interfaces, without adopting all their spellings, defaults, or matching modes.
shared dependency-ready scheduler defined below. This is settled independently
of the existing implementations and includes refactoring both resolvers.
The defaults and availability of these controls are settled. Their exact scope
at explicitly named roots, the definition of hidden names across filesystems,
and application-level policy precedence are described under Open Decisions.
`follow` and `hidden` are the proposed field spellings.
OD-1 (recursion default), OD-4 (discovery boundaries and cycles), OD-5 (policy
precedence), and OD-6 (absent-import evidence) are settled in the sections below.
OD-2 and OD-3 remain open. `follow` and `hidden` are the proposed field spellings;
their applicability to a retained `glob` selector remains part of OD-3.
## Decisions Resolved From Ansible
@@ -278,7 +278,11 @@ change resource identity or the effective configuration destination.
The selected model uses an explicit search root and recursion flag, rather than
introducing recursive `**` syntax or inferring a root from a wildcard prefix.
The default when `recursive` is omitted remains open; examples specify it.
**OD-1: settled.** Omitting `recursive` means false: search only direct files.
Authors explicitly opt into descendants. This makes the search scope visible
while keeping recursion independent of the permissive defaults for absence,
symlinks, and hidden entries.
The settled depth convention is:
@@ -289,7 +293,7 @@ The settled depth convention is:
- `recursive: false` selects only direct files regardless of a valid depth value.
- Depth controls discovery only, not namespaces, identities, or destinations.
Remaining validation and traversal choices are listed below. Recursive discovery
Remaining directive validation choices are listed below. Recursive discovery
must fit the existing filesystem containment, repeated-source, and load-budget
contracts rather than silently changing them. In particular, examined entries,
including nonmatches, still consume the visit budget; a small number of matches
@@ -298,11 +302,29 @@ constraint, not a new Ansible-derived decision.
### Symlinks And Hidden Entries
Directory searches default to `follow: true` and `hidden: true`. Authors may
disable either behavior independently of recursion, depth, matching, or absence
handling. `hidden: false` excludes hidden files and prunes hidden directories;
`follow: false` disables following symlinks within the discovery scope. The
precise boundary for explicit roots and file links remains open below.
**OD-4: settled.** Discovery controls are local to the declaration. Directory
searches default to `follow: true` and `hidden: true`, subject to the application
defaults described below. Authors may disable either behavior independently of
recursion, depth, matching, or absence handling.
Define hidden entries by a leading dot in the portable source namespace.
`hidden: false` skips discovered hidden files and prunes discovered hidden
directories. `follow: false` skips discovered symlink files and prevents descent
through discovered symlink directories. Explicitly named roots and separate
direct-file imports retain their existing source-access semantics. The controls
do not implicitly propagate to declarations inside imported files.
These are discovery-selection controls, not a load-wide access prohibition.
If OD-2 retains `glob` and OD-3 enables these controls on it, filtering applies
to wildcard-selected entries; explicitly named path components remain explicit
access. This conditional scope rule does not settle either open decision.
When following links, detect a canonical directory repeated on the active
traversal path and fail with a cycle diagnostic. Do not silently prune the loop
and continue. Existing dependency-cycle and repeated-source errors remain in
force; discovered aliases do not authorize silently deduplicating imported
sources. Directory-cycle handling retains the existing containment and
native-filesystem capabilities.
These controls narrow selection rather than authorize ignoring errors in a
selected source. `required: true` applies after discovery filters: if the search
@@ -313,8 +335,9 @@ not expose it.
## Illustrative Examples
These examples describe the proposed interface, not configuration accepted by
the current release. They assume the consumer has enabled imports. File paths
and directory roots resolve from the declaring file; destinations use the
the current release. They assume the consumer has enabled imports and has not
changed the library's policy defaults. File paths and directory roots resolve
from the declaring file; destinations use the
existing relative-placement rules.
### Import One File
@@ -360,8 +383,8 @@ search, the declaration can also include:
hidden: false
```
The exact treatment of an explicitly named root is an open scope decision, not
an additional change to the settled defaults.
These controls narrow discovery below the explicitly named root; the root
continues to resolve under the existing source-access rules.
### Require A Recursive Prompt Collection
@@ -397,8 +420,8 @@ directory searches, with `required: true` opting into failure on absence. This
deliberately supersedes the earlier suggestion to retain required-by-default
imports.
The table assumes the library's default absence policy. A consumer-selected
default or enforcement option remains open below.
The table assumes the library's default absence policy. Applications may select
a different default using the settled precedence rules below.
| Situation | Proposed behavior |
| --- | --- |
@@ -423,15 +446,36 @@ contribution.
## Who Chooses Strictness?
The general philosophy permits operators and consuming applications to choose
stricter behavior. Import authors can express local requirements through
`required: true`. This does not yet specify an application-wide override API.
**OD-5: settled.** Provide load-level defaults for `required`, `follow`, and
`hidden`. Resolve each applicable field independently using this precedence:
Keep the distinction between choosing defaults and overriding explicit intent.
If an operator or application should be able to strengthen or relax a requirement
declared in an imported document, define that capability and its precedence
separately. Optional-by-default alone does not imply that an authored requirement
can be overridden, or that requirements propagate to transitive imports.
1. Explicit value on the import declaration.
2. Application-selected default, when supplied.
3. Library default: false for `required`, true for `follow` and `hidden`.
Apply this rule to transitive declarations too. A declaration does not inherit
these field values from its importing parent. Applications may expose the
defaults to operators through their own interfaces; simple consumers need no
additional setup. Exact Go API spelling belongs to implementation planning.
Explicit declarations remain authoritative, permitting intentional exceptions
to stricter application defaults. Defaults are not enforcement controls. Do not
add options that override explicit declarations as part of this work without a
separate concrete requirement and scope decision. The fields' applicability to
each selector remains part of OD-3.
## Evidence For Absent Optional Imports
**OD-6: settled.** Record minimal structured inspection evidence when an
optional declaration selects nothing: its declaring source/location, selector,
and absent outcome. Emit no warning by default and do not fabricate a
loaded-source record. This keeps optional contributions quiet during ordinary
use while allowing applications to explain omissions during troubleshooting.
Decide the exact inspection record shape during implementation planning,
reusing existing structures where appropriate and retaining existing inspection
contracts. Include ownership tests for the new evidence. This addition does
not require applications to reconstruct absent-import provenance themselves.
## Release Boundary
@@ -448,38 +492,25 @@ scaffolding. No promise is made to preserve every old declaration or use case.
## Open Decisions
The recommendations below are not settled decisions. The new principles resolve
transition support, omitted filename filtering, and the defaults and opt-out
availability for symlinks and hidden entries. Shared dependency-ready ordering
for imports and `extends` is also settled. Six decision areas remain;
closely related validation details are grouped together without treating them
as implicitly approved. Each has at least one viable alternative.
Only OD-2 and OD-3 remain open. Their identifiers are preserved for continued
discussion. The recommendations below reflect the latest review against the
architecture principles; they are not settled decisions. In particular,
multiple patterns, retaining `glob`, and applying discovery controls to `glob`
remain proposals. OD-1, OD-4, OD-5, and OD-6 are settled above.
### 1. Recursion Default
**Why still open:** Recursion determines search scope independently of absence
handling and permissiveness. Ansible's `find` and `win_find` default to
nonrecursive searches; `include_vars` recursively loads directories by default.
**Recommendation:** Default `recursive` to false. A declaration explicitly
chooses whether subdirectories contribute. This makes the search scope visible,
at the cost of one extra field for recursive collections.
**Alternative:** Default to true, following `include_vars`. Collection authors
could reorganize files into subdirectories without revising declarations, but
adding a subdirectory could also add contributions to an existing import.
Neither choice changes the settled `follow`, `hidden`, or `required` defaults.
### 2. Matching Language And Selector Expressiveness
### OD-2. Matching Language And Selector Expressiveness
**Why still open:** Permissiveness does not select a matching grammar. Ansible
uses different wildcard and regex languages across its discovery interfaces.
Also, basename filters and path globs express different searches; the absence
of compatibility support does not decide which capabilities the final API needs.
**Recommendation:** Use one scalar `pattern` with the existing case-sensitive
Go `path.Match` grammar, applied to basenames. Keep `dir` as a literal root and
retain `glob` as a permanent, separate selector for wildcard directory segments
**Recommendation (not yet accepted):** Use the existing case-sensitive Go
`path.Match` grammar for basename matching. Accept a scalar `pattern` or a list
of patterns; a candidate matching multiple patterns in one declaration is
selected once. This does not suppress repeated-source errors from separate
inclusion routes. Keep `dir` as a literal root and retain `glob` as a permanent,
separate selector for wildcard directory segments
and exact-depth path selection. Its justification is useful expressiveness,
not old-document compatibility. Apply the new absence policy to every selector;
retaining `glob` must not introduce a legacy behavior mode. This supports both
@@ -492,29 +523,32 @@ Users needing selected directory names or depths would use multiple explicit
imports or application-side source selection. That is a real capability
tradeoff, acceptable if those use cases do not justify a separate selector.
**Filter alternative:** Allow a list of patterns unioned within one declaration,
and/or an explicit Go regex mode. This supports suffix unions or richer filters
without overlapping imports, but adds syntax, matching rules, and validation.
Separate scalar declarations are simpler, though overlapping selections remain
subject to source-repeat errors. Regex compatibility with Python or PowerShell
is not implied. None of these choices changes the settled all-filenames default
when a filter is omitted.
**Filter alternatives:** Restrict `pattern` to a scalar, keeping the parser
smaller but requiring separate declarations for suffix unions; overlapping
selections remain subject to source-repeat errors. An explicit Go regex mode
would support richer filters but add another dialect and validation rules.
Recommend deferring regex until a concrete requirement justifies it. Regex
compatibility with Python or PowerShell is not implied. None of these choices
changes the settled all-filenames default when a filter is omitted.
### 3. Directive Validation
### OD-3. Directive Validation
**Why still open:** Permissive discovery does not determine whether malformed or
inapplicable option values should be accepted. The draft's existing error
boundary remains: invalid declarations fail. We still need to define their
valid shapes, rather than infer extra restrictions from that boundary.
**Recommendation:** Accept nonnegative YAML integers for `depth`; zero remains
unlimited. Treat negative, null, string, boolean, and fractional values as
**Recommendation (not yet accepted):** Accept nonnegative YAML integers for
`depth`; zero remains unlimited. Treat negative, null, string, boolean, and fractional values as
malformed, even when recursion is disabled. Use exactly one selector per
import: `path`, `dir`, or `glob` if selected for the final API. Keep directory
search options (`pattern`, `recursive`, `depth`, `follow`, and `hidden`) on `dir`
and diagnose them on other forms. This makes likely mistakes visible, but
shared templates must omit inapplicable fields. It imposes no arbitrary maximum
depth. The removed `optional` spelling is not accepted under any variant.
import: `path`, `dir`, or `glob` if selected for the final API. Keep
`pattern`, `recursive`, and `depth` specific to `dir`; allow `follow` and `hidden`
on both `dir` and `glob` if the latter is retained. Diagnose unknown fields and
options that have no meaning for the chosen selector. This makes likely mistakes
visible, but shared templates must omit inapplicable fields. It imposes no arbitrary maximum
depth. The settled combination `recursive: false` with a valid larger `depth`
remains accepted and selects only direct files. The removed `optional` spelling
is not accepted under any variant.
**Alternative:** Normalize null depth to omission and negative integers to
unlimited, and permit unused directory controls on a direct-file declaration.
@@ -524,81 +558,3 @@ chosen independently; they do not require accepting ambiguous selectors.
Another viable selector shape is to allow multiple selectors as an explicit
union, but it needs rules for constructing the graph and handling overlaps that
one-selector declarations avoid.
### 4. Discovery Control Boundaries And Cycles
**Why still open:** Including hidden entries and following symlinks by default,
with options to disable both, is settled. The principles do not define hidden
names across filesystems, the reach of an opt-out at explicitly named roots, or
the response to recursive directory-link cycles. Ansible does not provide one
consistent set of answers across the inspected interfaces.
**Recommendation:** Define hidden entries by a leading dot for the portable
source namespace. With `hidden: false`, skip such files and prune such directories
encountered below the search root. With `follow: false`, skip encountered symlink
files and do not descend through encountered symlink directories. An explicitly
named `dir` root still resolves under existing source-access rules, as does a
separate `path` import. The controls affect this declaration's discovery; they
do not propagate to imports declared by selected files. This gives a simple
selection rule and preserves deliberate explicit access, but is not a ban on
all hidden or symlink access in the load.
When following links, detect a canonical directory repeated on the active
traversal path and fail with a cycle diagnostic. Keep existing errors for
imported sources reached repeatedly through aliases. This makes cycles visible
without silently deduplicating configuration. It adds directory-cycle handling
alongside the existing source-cycle rules; it does not change containment or
native-filesystem capabilities.
**Alternative:** Make the controls govern the explicit search root and every
component used to reach it too, and classify hidden entries using host attributes
where available. This can serve a broader exclusion policy, but adds filesystem
inspection requirements and differences between host and abstract filesystems.
A consumer-wide enforcement policy would also need explicit precedence rather
than being inferred from one declaration's flags.
**Cycle alternative:** Prune an edge back to an active ancestor directory and
continue. This permits link-rich trees without failing for an irrelevant loop,
but can hide a layout mistake and needs clearly defined inspection evidence.
It would not justify suppressing repeated-source errors for files actually
selected through distinct routes.
### 5. Application And Operator Policy Precedence
**Why still open:** The library defaults and authored opt-outs are settled.
Whether consumers can set different defaults or enforce restrictions over
explicit declarations remains an API choice. Ansible's task controls are not a
direct counterpart to policy over ConfigKit's transitive import graph.
**Recommendation:** Provide load-level defaults for `required`, `follow`, and
`hidden`, applied independently when a declaration omits the corresponding
field. Library defaults remain false, true, and true respectively. Explicit
per-declaration values win; imports do not inherit these values from their
parent declaration. Applications can expose defaults to operators through their
own interfaces. This enables stricter deployment choices while retaining
intentional exceptions, but does not enforce a universal exclusion rule.
**Alternative:** Offer enforcement options that override explicit declarations,
for example requiring every import to match or forbidding discovery through
symlinks throughout the load. This supports centrally imposed restrictions but
needs clear conflict diagnostics and scope, particularly for explicit sources.
A smaller alternative is declaration-level controls only initially: simpler
API, but consumers cannot choose common defaults without changing declarations.
### 6. Evidence For Absent Optional Imports
**Why still open:** Permitting absence does not settle how it should be exposed
for inspection. Ansible's file lookup, variable loader, and find modules return
different evidence, with no common absent-import model.
**Recommendation:** Record minimal structured inspection evidence when an
optional declaration selects nothing: declaring source/location, selector, and
absent outcome. Emit no warning by default and do not fabricate a loaded-source
record. This helps distinguish intentional absence from a typo, but requires an
inspection API decision and ownership tests. Decide its exact shape during
implementation planning, reusing existing structures where appropriate.
**Alternative:** Add no new inspection surface initially; retain loaded-source
evidence and let `required: true` turn absence into a diagnostic when needed.
This keeps the feature smaller, but successful optional omissions are harder to
investigate. Existing inspection contracts remain intact either way.