Revision history for Object::Configure - Runtime Configuration for an Object

0.24	Thu Aug 20 02:41:40 PM EDT 2026
	[ Enhancements ]
	- configure() now validates the class name against /\A[A-Za-z_]\w*(?:::\w+)*\z/
	  and croaks on invalid input, preventing taint-mode failures and injection
	  through malformed package names.
	- Added $RE_PATH_TRAVERSAL guard in configure() and _reload_object_config():
	  any config_file path containing ../ or /.. sequences is rejected with croak
	  before any filesystem probe, preventing directory traversal attacks.
	- disable_hot_reload() now checks PID > 1 && PID != $$ before sending SIGTERM,
	  preventing accidental signalling of PID 1 (init) or the parent process itself.
	- enable_hot_reload() and _run_config_watcher() now use defined-or (//) for the
	  interval parameter and clamp negative/zero values to $DEFAULT_INTERVAL,
	  preventing integer-wrap and busy-poll from a caller-supplied zero/negative value.
	- Memoised _get_inheritance_chain() with %_chain_cache (keyed by class name):
	  repeated calls for the same class return immediately, eliminating redundant
	  mro::get_linear_isa traversals. Benchmarks show ~7× speedup on repeated calls.
	- Memoised _find_class_config_file() with %_find_cache (keyed by NUL-joined
	  class+base+dirs): eliminates repeated filesystem probes for the same ancestor
	  class. Benchmarks show ~27× speedup on repeated calls.
	- Replaced grep { $_ eq 'UNIVERSAL' } @mro with List::Util::any (XS,
	  short-circuits on first match).
	- _find_class_config_file() now copies $dir to a local scalar before stripping
	  the trailing slash, avoiding aliased mutation of the caller's config_dirs array.
	- Added t/cgi_security.t: 17-subtest security pen-test suite covering path
	  traversal, null-byte injection, command injection, CRLF injection, env-prefix
	  collision, XSS, config_dirs traversal, DoS, object injection, registry
	  poisoning, signal safety, arbitrary file read, deep-merge DoS, and input
	  validation guard clauses.
	[ Bug Fixes ]
	- Fixed instantiate(): 'class' key was read from %params but not deleted, causing
	  it to propagate through configure() as a spurious config key and end up in the
	  blessed object's hash, polluting its namespace. Changed $params->{'class'} to
	  delete $params->{'class'} (D~ data-flow anomaly — dead store without delete).
	- Fixed class-name validation regex: \w+ after :: permitted digit-first components
	  (e.g. Bad::1Bad was accepted). Corrected to [A-Za-z_]\w* so each :: component
	  must start with a letter or underscore, matching Perl's actual identifier rules.
	- Fixed _reload_object_config(): the $RE_PATH_TRAVERSAL guard was positioned AFTER
	  the -f filesystem test, meaning traversal paths for non-existent files were silently
	  ignored rather than rejected. Guard now fires before the -f check.
	- Config::Abstraction, Log::Abstraction, and Return::Set all use eval internally
	  protect the caller's $@ from being clobbered by our internal eval blocks.
	- Fix https://github.com/nigelhorne/Object-Configure/issues/7

	- configure(): added $RE_PATH_TRAVERSAL guard for config_path (the env-only branch).
	  Previously, a caller with env-var control could set ClassName__config_path=../../etc/shadow
	  to force the hot-reload watcher to stat arbitrary system files (mtime side-channel via
	  SIGUSR1) and cause fatal taint violations under -T. Now rejected with croak before any
	  filesystem probe, matching the existing config_file guard.
	- register_object(): added blessed($obj) guard. The POD has always required a blessed
	  reference, but the code only checked defined(). An adversary or buggy caller could push
	  thousands of unblessed entries, causing reload_config() to iterate them on every SIGUSR1
	  and degrading throughput proportionally (DoS). Unblessed refs now croak immediately.

	[ Tests ]
	- Expanded t/edge_cases.t from 28 to 62 subtests: added destructive, pathological,
	  boundary-condition, and security subtests across all public and private functions:
	    * Param-type hostility: undef params defaults to {} (intentional); arrayref params
	      surfaces Perl type error (documented hostile-input behaviour).
	    * Global variable integrity: $@, $_, and alarm() are all verified not to be
	      clobbered by configure() across its full code path.
	    * Return-type contracts (Test::Returns): configure() satisfies { type => hashref }
	      schema; reload_config() satisfies { type => integer } schema.
	    * Security / injection: null byte, CR, LF, and shell metacharacters (space ; | $)
	      in class name are rejected; _reload_object_config() traversal guard verified to
	      fire before -f filesystem check even for non-existent traversal paths.
	    * Filesystem hostility: directory as config_file (graceful), dangling symlink
	      (croaks with locale-safe OS error), unreadable file / chmod 000 (locale-safe
	      EACCES croak), config_dirs entry that is a plain file (ignored), empty-string
	      config_dirs entry (no crash).
	    * _deep_merge() boundary: (undef,undef)->undef, (undef,hashref)->overlay,
	      (hashref,scalar)->scalar, (hashref,arrayref)->arrayref, 50-level nesting no stack
	      overflow.
	    * Registry/signal safety: register_object(undef,*) and register_object(*,undef)
	      croak with usage; unblessed hashref now croaks (security fix S2);
	      disable_hot_reload() idempotent; enable_hot_reload(0/-9999) clamped to default;
	      double enable_hot_reload() is a no-op (no double-fork).
	    * Upstream failure (Mockingbird): Config::Abstraction::new returning 0 causes
	      carp and continues; configure() returns a valid hashref.
	    * DoS resilience: 500-key params hash handled without crash.
	    * Context safety: configure() in list context returns exactly one hashref.
	    * Regressions: digit-first :: component still rejected; traversal guard fires
	      before -f for non-existent paths; instantiate() with undef/missing class croaks;
	      caller-supplied _config_file/_config_files keys preserved in result.
	- Added t/data-flow.t: 24 Define-Use (DU) chain subtests covering %stashed_values
	  stash-restore, $array_logger priority, %_find_cache memoization (both hit and
	  undef sentinel), %_chain_cache copy semantics, @config_files_to_load sort order,
	  _deep_merge non-mutation of inputs, carp_on_warn propagation, weak-ref GC
	  lifecycle, reload_config() dead-ref pruning, caller params non-mutation,
	  _config_file set-once semantics, %_config_file_stats synchronous population,
	  env_prefix :: → __ conversion, %tracked_files duplicate-file guard, O~ file
	  descriptor leak check (/proc/self/fd), $@/$_ non-pollution, _build_logger()
	  paths (undef/NULL/passthrough), and instantiate() 'class' key non-leakage.
	- Expanded t/integration.t from 19 to 27 subtests: added optional-deps subtest
	  (Test::Without::Module), env-var precedence E2E, GC weak-ref pruning verification
	  (exercises the reload_config() bug fix), arrayref logger capture, logger isolation
	  between two concurrent objects, 3-level inheritance merge order, _config_file_stats
	  population, and security E2E (traversal + invalid class in full constructor workflow).
	  Key fixes applied during test writing:
	    - Arrayref loggers must use ->warn() (not ->debug()): 'debug' has syslog priority 7,
	      above the default Log::Abstraction threshold (warning = 4), so debug calls are
	      silently dropped; hashref logger specs are also not stashed before the config
	      merge, so a site-local UNIVERSAL logger config can override an explicit level.
	    - _config_file_stats is only populated via the primary-file branch (-r $config_file);
	      the fallback directory scan does not record stats.  Tests that verify stat tracking
	      must supply an absolute config_file path.
	- Expanded t/function.t from 35 to 55 subtests: added white-box coverage for the
	  S2 class-name guard (8 invalid partitions + 2 valid boundary cases), S1 path-traversal
	  guard, _reconfigure_logger() (2 subtests), _reload_object_config() (3 subtests
	  including traversal and private-key filtering), register_object() weak-reference
	  storage, restore_signal_handlers() after synthetic install, enable_hot_reload()
	  early-return guard, _find_class_config_file() trailing-slash non-mutation,
	  _get_inheritance_chain() memoisation cache, _deep_merge() arrayref replacement,
	  configure() env-var merge flow, and Test::Memory::Cycle circular-reference check.
	- Expanded t/unit.t from 35 to 48 subtests: added API message ledger (tracks all
	  POD-documented error/warning messages; final subtest asserts ledger is empty),
	  coverage for the new invalid-class and path-traversal croak messages, mocked
	  Config::Abstraction::new to trigger the "Warning: Can't load configuration" carp,
	  $@ preservation test, alarm() non-interference test, _config_files metadata,
	  arrayref-logger priority over config-file logger, and instantiate() hot-reload
	  registration side-effect.
	- Added t/domain.t: 41 EP/BVA subtests covering all input domains for all public
	  and private functions: class-name valid/invalid partitions, config_file/config_path
	  traversal boundaries, logger spec types (undef/NULL/arrayref/hashref/blessed),
	  register_object() unblessed-croak boundary (security fix S2), _deep_merge() type
	  combinations, _build_logger() all spec types, and combinatorial edge cases.
	- Added t/path.t: 52 CFG path-coverage subtests exhausting every branching path
	  through configure(), register_object(), reload_config(), enable_hot_reload(),
	  disable_hot_reload(), _build_logger(), _find_class_config_file(), _deep_merge(),
	  _reload_object_config(), and _reconfigure_logger() — including signal-handler
	  chaining, config_path env-only stat branch, and injected upstream failures.
	- Added t/transaction.t: 14 multi-step lifecycle subtests verifying state
	  consistency at every phase boundary and correct behaviour on mid-flight
	  failures: configure() full pipeline (T1), C::A failure rollback (T2),
	  env-override precedence chain file<env (T3), register→reload→update
	  synchronous hot-reload flow (T4), GC weak-ref pruning on reload (T5),
	  multi-object reload with partial GC (T6), same-class push semantics (T7),
	  nonexistent-config resilience (T8), _reload_object_config exception isolation
	  (T9), instantiate() lifecycle (T10-T11), and enable/disable watcher lifecycle
	  including idempotency (T12-T14).

0.23	Sun Jun 28 09:55:21 EDT 2026
	[ Refactoring ]
	- Replaced hand-rolled _walk_isa / _get_inheritance_chain with mro::get_linear_isa
	  (handles diamond inheritance correctly; UNIVERSAL appended manually when absent).
	  _walk_isa is removed; callers that called it directly will get "Undefined subroutine".
	- Extracted _build_logger() private helper consolidating logger creation from
	  configure() and _reconfigure_logger(), eliminating the duplication.
	- Added Readonly constants for all magic strings: $OS_WINDOWS, $LOGGER_NULL,
	  $SIG_DEFAULT, $SIG_IGNORE, $POLL_SLEEP, $KILL_TIMEOUT.
	- Removed duplicate dead-code block in configure() (second identical
	  "if ($array && !$params->{'logger'}->{'array'})" check).
	- Replaced POSIX::WNOHANG() fully-qualified call with imported WNOHANG.
	- Added =head1 LIMITATIONS POD section documenting known design trade-offs.
	- Readonly promoted from TEST_REQUIRES to PREREQ_PM (now used in lib/).

	[ Bug Fixes ]
	- Fixed configure() to give user-supplied arrayref logger priority over
	  config-file logger (previously the config file could silently override
	  an explicitly passed logger => \@arr).
	- Fixed env-var ancestor merge order in configure() no-config-file path:
	  was iterating child-first (base-class env vars overrode child-class),
	  now iterates base-first so child correctly overrides base.
	- Fixed _reload_object_config() logger-key match from /^logger/ to eq 'logger':
	  keys like 'logger.file' are flat config values, not logger specs, and must
	  not be processed through _reconfigure_logger().
	- Fixed warn → carp in reload_config() for package-correct stack traces.

	[ CI ]
	- Bumped App::Test::Generator to 0.41 in both dashboard.yml and mutate.yml.
	- Added --exclude lib/Devel --exclude lib/App/Test/Generator/Sample to the
	  non-ATG-repo generate-test-dashboard call in dashboard.yml (parity with
	  mutate.yml).
	- Fixed empty-BASE guard in mutate.yml (no-op diff when all commits are bot).
	- Added conditional $BASE_SHA_FLAG in mutate.yml (avoids passing empty --base_sha).
	- Removed || true from all git pull --rebase lines in both workflow files.
	- Fixed coverage snapshot to use pre-captured SNAPSHOT_SHA / SNAPSHOT_TIMESTAMP
	  instead of recomputing git rev-parse after bot commits moved HEAD.

	[ Tests ]
	- Added t/locales.t: verifies configure() error strings are locale-consistent
	  under en_US.UTF-8, de_DE.UTF-8, ja_JP.UTF-8 using local $! = ENOENT pattern.
	- Updated t/function.t: replaced _walk_isa() subtests with _get_inheritance_chain()
	  equivalents to match refactored internals.

0.22
	[ Bug Fixes ]
	- Fixed https://github.com/nigelhorne/Object-Configure/issues/4

0.21	Thu May 21 07:55:13 EDT 2026

	[Bug Fixes]
	- Bump minimum version of Params::Get and Config::Abstraction

0.20	Tue May 19 08:51:20 EDT 2026

	[Enhancement]
	- Improve the method documentation

	[Bug Fixes]
	- Fixed disable_hot_reload() hanging indefinitely when child process does not exit on SIGTERM
		During mutation testing the forked watcher process can be left in a state where it never receives or acts on SIGTERM, causing waitpid to block forever.
		The fix replaces the unconditional waitpid with a non-blocking poll loop with a 5-second deadline, then escalates to SIGKILL if the child is still alive, followed by a final waitpid that is safe because SIGKILL cannot be caught or deferred.
	- Fixed configure() logger dispatch ignoring NULL and re-wrapping existing Log::Abstraction instances
	- Fixed looking in the correct directory for a object class to reload
	- Fixed _reload_object_config() to use full config file paths during hot reload.
		Previously attempted to reload from basename only (e.g., 'app.yml') instead
		of full path stored in _config_files array. This caused -f test to fail and
		reload to silently return without updating object properties or calling
		_on_config_reload hooks. Now uses the last (most specific) path from
		_config_files array when available, falling back to _config_file for
		backward compatibility. Added explicit return statement for consistency.
	- Sanity check that class is given to configure()
	- Fixed register_object() to return void consistently.
		Previously returned a coderef on first call (from $SIG{USR1} assignment),
		undef on Windows, and false on subsequent calls. Now explicitly returns
		nothing via 'return;' at end of function to match API specification.
	- Fixed restore_signal_handlers() to return void consistently.
		Previously returned undef when handler was defined and empty list when
		not defined. Now explicitly returns nothing via 'return;' at end of
		function to match API specification.
	- Automatically preserve coderefs and blessed objects passed to configure().
		Config::Abstraction treats unknown scalar values as config file paths,
		which corrupts coderef and object references. The configure() function
		now automatically stashes these values before processing and restores
		them afterward, eliminating the need for users to implement manual
		stash-delete-restore patterns in their constructors. The logger
		parameter continues to receive special handling for wrapping in
		Log::Abstraction. Added comprehensive test suite (t/coderef.t) to
		verify preservation of coderefs and blessed objects.
	- Fixed _deep_merge() to correctly return overlay value when overlay is not a hash.
		Previously returned base value instead of overlay, violating the principle
		that overlay should always take precedence. Changed line to
		'return $overlay unless ref($overlay) eq "HASH"'.
	- Fixed logger='NULL' handling to prevent unwanted logger creation.
		When logger parameter was set to 'NULL', code would skip wrapping but fall
		through to else clause which created a default Log::Abstraction anyway.
		Restructured logic to explicitly check for 'NULL' first and preserve it
		without creating any logger. Also optimized to avoid dereferencing
		$params->{'logger'} twice by using the already-assigned $logger variable.
	- Comprehensive POD documentation updates for all public methods.
		Added detailed documentation for configure(), instantiate(),
		enable_hot_reload(), disable_hot_reload(), reload_config(),
		register_object(), restore_signal_handlers(), and get_signal_handler_info().
		Each method now includes Purpose, Arguments, Returns, Side Effects, Notes,
		Usage Example, API Specification (Params::Validate::Strict compatible
		input schema and Return::Set compatible output schema), and Formal
		Specification (Z notation). Added comprehensive white-box test suite
		(t/function.t) covering all public and private functions.

0.19	Fri Dec 12 08:07:32 EST 2025

	[Enhancement]
	- Added UNIVERSAL configuration inheritance support.
	All classes now automatically inherit from a universal.yml (or .conf,
	.json, etc.) configuration file if present. This allows application-wide
	defaults to be set once and inherited by all classes unless explicitly
	overridden. Modified _walk_isa() to explicitly include UNIVERSAL in the
	inheritance chain for classes with no explicit parents. Added test suite
	(t/universal.t) to verify UNIVERSAL inheritance across multiple classes
	and inheritance levels. Updated POD with UNIVERSAL CONFIGURATION section.

0.18	Thu Dec 11 11:58:01 EST 2025
	If a parent class hierachy can't be found, ensure we still load the given path/file

0.17	Sat Dec  6 21:31:58 EST 2025
	Load in parents classes as well
	Don't assume English - fixes https://www.cpantesters.org/cpan/report/b1624b76-ad2b-11f0-852b-311a6e8775ea

0.16	Thu Oct 16 19:10:49 EDT 2025
	Set croak_on_error
	Latest testing console

0.15	Wed Sep 17 07:28:24 EDT 2025
	Allow the logger to be NULL, in which case nothing is set up
	Scalar::Utils was used but not imported
	instantiate: avoid double blessing
	Added testing dashboard on GitHub Pages
	Pass schema through to Config::Abstraction
	Added configuration hot reloading without restarting the application (not supported on Windows)

0.14	Wed Aug 27 13:16:36 EDT 2025
	Allow the params to be undef

0.13	Wed Aug 20 09:39:00 EDT 2025
	Ensure it works with Log::Abstraction 0.25 and bump to use that
		Also fixes https://github.com/nigelhorne/Object-Configure/issues/2

0.12	Fri Aug  1 08:20:23 EDT 2025
	Use Return::Set
	Allow carp_on_warn to be read from the configuration file, and change the default to 0

0.11	Mon Jul 21 08:06:15 EDT 2025
	Avoid encapsulating Log::Abstraction within Log::Abstraction

0.10	Wed Jun 18 14:54:59 EDT 2025
	Use Config::Abstraction 0.10 to test environment settings

0.09	Wed Jun 18 09:09:58 EDT 2025
	Fix tests in other languages

0.08	Fri Jun  6 08:35:54 EDT 2025
	Removed a bunch of unneeded pre-reqs
	Added the instantiate method

0.07	Thu May 29 09:35:38 EDT 2025
	Renamed from Class::Debug to Object::Configure
	Get the Abstract from the PM file
	Give better error message

0.06	Thu May 22 21:11:29 EDT 2025
	Ensure loggers aren't lost when it's a simple list

0.05	Tue May 20 21:21:09 EDT 2025
	Fix CI

0.04	Tue May 20 21:14:56 EDT 2025
	Try harder to avoid a logger hash within a logger hash

0.03	Fri May 16 11:45:00 EDT 2025
	Added CI support
	Added better reporting on error
	Use the features of Config::Abstraction 0.25

0.02	Wed May  7 17:08:29 EDT 2025
	Fix the return value
	Added testing

0.01	Wed May  7 16:42:04 EDT 2025
        First draft
