ADR-015: Configuration merge and exclusions¶
Status¶
Accepted; amended by ADR-016 (D3).
Decision¶
D1. An unusable configuration file exits 2. Invalid YAML, an unknown or
repeated key, or a wrongly typed value stops the run before any repository file
is opened, and nothing is partially applied: the file is part of the invocation,
so a broken one is a usage error like a misspelled flag. A number or a flag is never read from
text or from another type (allow_dirty: "yes", timeout_s: "60" and max_file_bytes: 1000.0
are wrongly typed), a YAML set is refused because it has no order, and a file over 1 MiB is
refused before it is parsed. Each is a refusal, never a traceback, and one about a value names its
key or line.
D2. --verify replaces the file's list; it does not append to it. The
override list is then non-empty exactly when every command in the run is level
1, so ADR-007's trust ladder needs no per-command provenance, and no command the
repository chose runs where you named your own.
D3. A bare boolean flag adds permission and never removes it.
--allow-dirty has no negative spelling, so allow_dirty: true in the file
stands; otherwise every flag left unwritten would silently reset a setting.
D4. The always-excluded set is a published closed list, matched first, and no
exclude can undo it. Eleven patterns, in docs/CLI.md and
obelize.config.ALWAYS_EXCLUDED, asserted equal in both directions. .obelize/
is a correctness property: without it a second scan finds the report the first
wrote. A ! pattern re-includes only what the user's own exclude excluded.
D5. A verification command that needs a shell is refused when it is read.
Commands run with shell=False (ADR-007), so one whose shlex split yields a
bare operator such as |, &&, ;, > or <, or does not split at all, is
refused and told to ask for a shell: sh -c '<command>', or cmd /c "<command>" on Windows
(ADR-049). Tokens are checked, not the raw string, so python -c "print(1>2)" passes.
D6. The fixtures/ directory of a loaded pack that sits inside the repository is left out of
every scan. Its fixtures are legacy code by design, so a scan that entered them would plan edits
to them. It is skipped as the always-excluded set is, not as an exclude is (an excluded file that
imports the legacy distribution would hold the pin, and a fixture is not the repository's code).
It is the fixtures/ directory beside the pack file, which every fixture path of a pack is under
(ADR-018), and no more: a file the pack sits beside is the repository's own and stays scanned, or
the pin could move past a legacy import nothing read. A pack at the repository root gives up
nothing, and neither does a pack the wheel carries, which the user did not supply. REPORT.md
names the directory. It is not a configuration key: a repository's .obelize.yml cannot add to
it.
Consequences¶
- The configuration models live in
obelize.modelsand loading inobelize.config, so the evidence writer never imports a YAML parser. - Only the always-excluded set and a loaded pack's
fixtures/(D6) prune a directory; a user'sexcludefilters files and prunes nothing (ADR-016 D3). model.base_urlmay not carry a credential, sodocs/RUN_FOLDER.md's recorded host anddocs/PRIVACY.md's never-written key stay true together..obelize.yamlis refused by name rather than ignored in silence.- No key turns the always-excluded set off;
build/is the pattern likeliest to hide a package a user wanted scanned.