Revision history for Config::Abstraction - Merge and manage configuration data from different sources

0.40	Sun Aug  2 04:37:58 PM EDT 2026
	[ Security ]
	- Fix YAML code injection: YAML::XS v0.91 ignores $YAML::XS::DisableCode and
	  materialises callable CODE refs from !!perl/code tags. New private helper
	  _sanitize_yaml_values() recursively replaces CODE and GLOB refs with undef
	  after every LoadFile/Load call. Do not rely on DisableCode for security.
	- Fix XML external entity (XXE) injection at all four XMLin() call sites: file
	  content is now pre-read and checked for <!ENTITY ... SYSTEM|PUBLIC declarations
	  before parsing; XMLin receives a string reference rather than a filename so no
	  entity is resolved. Files containing external entity declarations are skipped
	  with a carp warning.
	- Fix XXE bypass via Config::Auto fallback: after the XML XXE guard blocked a
	  file and set $data to undef, YAML::XS returned a non-HASH scalar (truthy),
	  causing the if(!$data) fallback chain to run; Config::Auto then re-read and
	  expanded the entity. Fixed by saving raw file content before the parsers run
	  and conditioning the entire fallback chain on the absence of entity declarations.
	- Fix encryption_key_file silent failure: when encryption_key_file was specified
	  explicitly in the constructor but the named file did not exist, the module
	  silently fell back to unencrypted operation. The constructor now croaks
	  immediately. Files sourced from environment variables still fail silently.

	[ Performance ]
	- _flatten_keys: eliminate O(N^2) hash copying by delegating to new private
	  _flatten_into helper that writes each leaf key into a caller-supplied
	  accumulator hashref exactly once; benchmarks show 2x speedup on a 60-leaf
	  config (20 000 iter/s -> 40 000+ iter/s).
	- get()/exists(): cache the compiled sep_char regex as $self->{'_sep_re'} at
	  construction time instead of recompiling qr/\Q$sep_char\E/ on every key
	  lookup; benchmarks show ~50% speedup on repeated get() calls.
	- _is_local_host(): cache Sys::Hostname::hostname() result on the object
	  ($self->{'_cached_hostname'}) so the syscall is made at most once per
	  Config::Abstraction instance, not on every directory entry evaluation.

	[ Bug Fixes ]
	- Fix AUTOLOAD method dispatch using stale $self->{data} (raw constructor
	  defaults) instead of the fully merged $self->{config}; file and environment
	  overrides were silently invisible through AUTOLOAD accessor calls.
	- Fix AUTOLOAD in flatten mode: Hash::Flatten uses '.' as separator regardless
	  of sep_char, so the sep_char-separated method name is now translated to dotted
	  form before lookup in the flat config hash.
	- Fix _load_remote_dir using a hardcoded file list that omitted TOML files and
	  the new environment-specific tiers (base.{env}.* / local.{env}.*); the
	  dynamic @_file_list is now passed from _load_config to _load_remote_dir.

	[ Enhancements ]
	- Add Readonly constants for AES-256-GCM sizes ($_AES_NONCE_SIZE,
	  $_AES_TAG_SIZE, $_AES_KEY_SIZE, $_ENC_PAYLOAD_MIN, $_HEX_KEY_LEN,
	  $_B64_KEY_MIN, $_B64_KEY_MAX) replacing inline magic numbers.
	- Add =head1 LIMITATIONS documenting: separator escaping, Data::Reuse status,
	  Windows env-var case sensitivity, AUTOLOAD sep_char requirement, and TOML
	  in remote directories.
	- Add full POD (EXAMPLE / API SPECIFICATION / MESSAGES / FORMAL SPECIFICATION /
	  PSEUDOCODE) to get(), exists(), all(), encrypt_value(), and prefer_*().
	- New t/locales.t: geographic (ISO-3166 country-code environment tiers) and
	  POSIX locale subtests; uses local $! = ENOENT; my $msg = "$!"; idiom to
	  obtain locale-specific strings without POSIX::strerror.
	- Declare CryptX as an optional_features entry in META so installers can prompt
	  users who want AES-256-GCM encryption support.
	- New C<environment> constructor option: activates two additional file tiers --
	  base.{env}.* (loaded immediately after base.*) and local.{env}.* (loaded
	  immediately after local.*). When not supplied, auto-detected from
	  {env_prefix}ENV (e.g. APP_ENV), then PLACK_ENV, then NODE_ENV.  Missing
	  env-specific files are silently skipped; invalid names (anything other than
	  /^[A-Za-z0-9_\-]+$/) cause a croak.
	- New C<validators> constructor option: a hashref mapping dotted config keys to
	  per-key validation rules. Each rule may be a type-name string (integer, number,
	  float, boolean, string, array, hash), a compiled regex, a coderef, or a hashref
	  combining type/pattern/min/max/required constraints. Violations croak immediately
	  after the merge (or on first access when lazy => 1).
	- New C<checker> constructor option: a YAML string or hashref prototype passed to
	  Config::Checker for template-based structural validation. Config::Checker is
	  optional; if absent a carp warning is emitted and validation is skipped.
	- TOML file support: base.toml and local.toml are now discovered
	  automatically in config_dirs alongside YAML/JSON/XML/INI equivalents;
	  TOML is also tried as a fallback parser in the all-parsers chain used
	  for extensionless config_file entries.  Requires TOML::Tiny (TOML 1.0).
	  If TOML::Tiny is absent the module gracefully skips .toml files.
	- New C<lazy> constructor option: when true, all source discovery and file
	  I/O are deferred until the first accessor call (get/exists/all/explain_sources/
	  prefer_*/merge_defaults/AUTOLOAD), reducing startup overhead for applications
	  that may not always consume the configuration.  With lazy loading the
	  constructor always returns a blessed object; schema validation likewise
	  runs on first access rather than at construction time.
	- Remote configuration via the Newcastle Connection convention: entries in
	  config_dirs beginning with /../hostname/path are fetched from the named
	  host over SSH using File::Slurp::Remote, processed through the same
	  format-detection and Hash::Merge pipeline as local files, and merged in
	  the same position as their equivalent local directory would be
	- Local-host short-circuit: /../localhost/, /../127.0.0.1/, /../::1/, and
	  /../$hostname/ (FQDN and short form, case-insensitive) are unwrapped to
	  their plain directory path and read from disk directly -- no SSH connection
	  is made, so a shared config entry degrades gracefully on the host itself
	- New dependency: File::Temp (used to parse INI content fetched as a string,
	  since Config::IniFiles requires a real filesystem path)
	- Document the Newcastle Connection convention and the rationale for choosing
	  the /../ prefix over hostname:/path, file://, ssh://, and plain hostname/path
	- Honour $CONFIG_DIRS in the environment
	- New public method explain_sources(): returns a hashref keyed by dotted
	  config key; each value carries the final merged value and an ordered list of
	  {type, label, value} source records showing every source that contributed to
	  that key, from lowest to highest precedence
	- New public methods prefer_env(), prefer_file(), prefer_data(), prefer_argv():
	  return the value that a specific source layer contributed to a key, bypassing
	  higher-precedence sources; fall back to get() when that layer did not set the key
	- _load_config() and merge_defaults() now save and restore
	  Hash::Merge::get_clone_behavior() around their set_clone_behavior(0) calls,
	  preventing the no-clone global state from leaking into unrelated merge() calls
	  and fixing a "Can't store CODE items" crash when data contains coderefs

	[ Bug Fixes ]
	- merge_defaults() was permanently mutating $self->{'config'} via
	  'delete $config->{global}'; fixed by working on a shallow copy
	- Windows: skip Newcastle Connection localhost subtest when tempdir() returns
	  a drive-letter path (C:\...) that cannot be embedded in /../localhost$dir
	- Windows: skip unreadable-file subtests in extended_tests.t where
	  chmod(0000, $file) is a no-op and files remain readable

0.39	Sun May 24 16:16:50 EDT 2026

	[ Bug Fixes ]
	- Don't use fixate for now, what works on Linux doesn't work on MACOS
		and vice versa.  I don't know why.

0.38	Wed May 20 21:46:58 EDT 2026

	[ Enhancements ]
	- Document the "defaults" argument to new()

	[ Bug Fixes ]
	- Fix fatal croak on malformed or degenerate YAML/JSON files in config_dirs;
		parse failures now warn and skip the file rather than aborting construction,
		consistent with existing XML and INI error handling
	- Fix corruption of coderefs and blessed objects passed via the 'data' argument;
		added _is_plain_scalar() guard to skip non-string scalars in the YAML colon-file
		value-munging loop, and fixed Data::Reuse::fixate() being called with a
		dereferenced hash instead of a hashref in get()
	- Fix Data::Reuse::fixate() crash in get() by passing hashref scalar directly
	  rather than dereferencing, and guard against double-fixation with !tied check
	- Fix exists() to return explicit 0 rather than empty string in flat mode
	- Fix circular initialisation crash when logger option is provided:
		Log::Abstraction is now bootstrapped directly in new() rather than
		via _load_driver(), which would attempt to use the logger before
		it was fully set up

0.37	Mon Dec  1 07:43:07 EST 2025
	Only use Data::Reuse if it's installed - its installation often fails
	Added the --version option to config-dump

0.36	Wed Oct 15 08:12:49 EDT 2025
	Ignore files that don't parse
	Added the exists() method

0.35	Mon Oct 13 18:38:37 EDT 2025
	Bump some minimum versions - GitHub#6
	Fix remote possibility where STDERR could close if Config::Abstract dies

0.34	Fri Sep  5 08:37:21 EDT 2025
	Added bin/config-dump
	If schema is passed in, validate the config file against it

0.33	Thu Jul 17 08:19:41 EDT 2025
	The config_path value wasn't being stashed properly

0.32	Wed Jun 18 14:47:07 EDT 2025
	Bash doesn't allow colons in variable names, so use _ everywhere

0.31	Tue Jun 17 14:52:57 EDT 2025
	Fix GitHub#4

0.30	Thu Jun  5 15:30:25 EDT 2025
	Try harder to ensure the running script isn't loaded as its own configuration
	Test argv and note that argv now need to start with --

0.29	Mon May 26 20:55:38 EDT 2025
	Also look in ${script_name}.cfg, ${script_name}.conf and ${script_name}.config
	Allow new() to accept just one parameter - the name of a file

0.28	Sat May 24 09:00:10 EDT 2025
	Look in the current directory, unless given an absolute path

0.27	Fri May 23 17:04:52 EDT 2025
	Look in generic DOCUMENT_ROOT/../conf before site specific DOCUMENT_ROOT/conf
	Added the level argument to new()
	Look in the current directory

0.26	Fri May 16 21:10:49 EDT 2025
	Change location of bug reports to RT
	Bumped minimum version of XML::PP (GitHub#1 - ANDK)
	Turn off clone behavior

0.25	Thu May 15 07:57:07 EDT 2025
	Added the 'merge' option
	Improved the documentation of the merge_defaults routine

0.24	Mon May 12 16:15:09 EDT 2025
	Added the 'no_fixate' option

0.23	Mon May 12 12:01:49 EDT 2025
	Fix tests on machines that have configuration files installed elsewhere
	Look in more places for the config files, when config_dirs is not given
	Consider $script_name for configuration data as well
	'file' means 'config_file'

0.22	Fri May  9 14:31:28 EDT 2025
	Fix CI tests

0.21	Fri May  9 13:55:23 EDT 2025
	Handle YAML crashes on reading an INI file

0.20	Tue May  6 20:07:40 EDT 2025
	Added merge_defaults()

0.19	Tue May  6 13:48:46 EDT 2025
	Make the data passed to the contructor be the default values

0.18	Tue May  6 08:17:31 EDT 2025
	Use the file config_dirs/default as the base
	Honour CONFIG_DIR

0.17	Mon May  5 07:45:21 EDT 2025
	Try harder to find the files

0.16	Thu May  1 07:25:25 EDT 2025
	Handle mixed case environments a bit better

0.15	Tue Apr 29 20:24:10 EDT 2025
	Makefile.PL: Prefer XML::PP, since XML::Simple can have loading problems on Windows
	Improved the fallback to Config::Auto when XML::Simple isn't working
	new() now fails, silently, if there's no config file
	Use Config::Abstract to load .pl files, but note RT#164587

0.14	Mon Apr 28 11:17:20 EDT 2025
	Allow logger to be an array ref
	Fail more gracefully
	Fall back to XML::PP if XML::Simple can't be loaded
	Config_path is now a list

0.13	Tue Apr 22 15:08:00 EDT 2025
	Added the data option to new()
	sep_char option to new() wasn't being remembered
	Added AUTOLOAD support
	Fix typo - 'durs' should read 'dirs'

0.12	Mon Apr 21 10:29:54 EDT 2025
	Allow environment variables to use '::' to separate fields

0.11	Mon Apr 21 08:56:35 EDT 2025
	Make the environment variable override simpler for simple cases

0.10	Sun Apr 13 21:45:12 EDT 2025
	Check with JSON::Parse first
	Use Data::Reuse, but note RT#100461
	Note the filetype

0.09	Sat Apr 12 15:48:48 EDT 2025
	Better fix for old Perls

0.08	Sat Apr 12 10:59:22 EDT 2025
	Fix code on older Perls

0.07	Thu Apr 10 19:40:41 EDT 2025
	Change the default for config_dirs to $HOME/.conf, $DOCUMENT_ROOT/conf, ./conf
	In YAML files, split comma separated entries
	Honour path as well as config_dirs

0.06	Wed Apr  9 08:30:55 EDT 2025
	Put the paths used to load the configuration file into config_path
	Only load the modules that are needed
	Improved logging of errors
	Added _load_driver

0.05	Tue Apr  8 09:50:15 EDT 2025
	Added tests for all()
	Added the logger option to new()
	Added the config_files option

0.04	Mon Apr  7 21:06:26 EDT 2025
	Better handling of config_file mode when an XML file is missing its header
	Handle more drivers
	Added the sep_char option

0.03	Mon Apr  7 16:22:16 EDT 2025
	Use Params::Get
	Needs Test::Most
	Improved handling of XML files

0.02	Mon Apr  7 12:50:34 EDT 2025
	Added config_file option

0.01	Mon Apr  7 11:45:31 EDT 2025
        First draft
