# Implementation Plan - [ ] 1. Foundation: rc-file path resolution and line-loading capability - [x] 1.1 Implement rc-file path resolution following XDG precedence, with unit tests - Resolve the rc-file location using the XDG config variable when it is set and non-empty - Fall back to the home-directory XDG default when that variable is unset or empty, and to "no resolvable path" when neither variable is usable - Wire the new module and its test file into both existing build targets that need it, so both compile and the new unit tests run under the existing unit test suite - Save each of `$HOME`/`$XDG_CONFIG_HOME`'s prior state (unset vs. empty vs. a value) before any test case mutates it, and restore that exact prior state afterward, since `controller_unit_test` runs 80+ test files sequentially in one process and a leaked env var would silently affect unrelated tests - Observable: a unit test run demonstrates the correct location is chosen for each of the three environment combinations above, and a subsequent unrelated test's own `$HOME`/`$XDG_CONFIG_HOME` reads are unaffected - _Requirements: 1.1_ - [x] 1.2 Implement rc-file line-loading with a pluggable command runner, with unit tests - Read the rc file line by line using `getline()` (no fixed-size buffer), invoking a caller-supplied runner once per line with the trailing newline removed - Treat a missing rc file as a no-op success (no error), and a present-but-unreadable rc file as a non-fatal warning that still allows startup to proceed - Stop reading immediately the first time the runner signals a stop, and propagate that signal to the caller without processing any remaining lines - Observable: a unit test run demonstrates every line is processed when the runner keeps succeeding, remaining lines are skipped the first time the runner signals a stop, the runner is never invoked when the file is absent, and a line well beyond a typical small fixed-buffer size (e.g. >4KB) reaches the runner intact and unsplit - _Requirements: 1.2, 1.3_ - [ ] 2. Core: xce_test startup integration - [x] 2.1 Add a flag that skips rc-file loading for a single invocation - Register the flag with xce_test's existing command-line option parsing so it is documented in xce_test's own help output - When the flag is given, record that rc-file loading should be skipped for this run - Observable: xce_test's help output lists the new flag with a description, and invoking xce_test with the flag parses successfully and records the skip state (verified directly, independent of task 2.2's wiring) - _Requirements: 2.1, 2.2_ - _Boundary: test.c::options[]/parse_opt_ - [x] 2.2 Wire rc-file loading into xce_test startup for both interactive and scripted sessions - After xce_test determines whether the session is interactive or scripted and before it reads the first line of its normal session, resolve and load the rc file unless the skip flag was given, using the session's existing command-execution path as the per-line runner - When rc-file loading signals a stop, exit using the session's existing pass/fail exit-code convention without entering the normal session loop - Observable: a valid rc file's settings/context are visible once the normal session begins, in both a piped run and an interactive run; the same is confirmed absent when the skip flag is given - _Requirements: 1.4, 1.5, 2.3_ - _Depends: 1.1, 1.2_ - _Boundary: test.c::main_ - [ ] 3. Validation: end-to-end behavior against the built binary - [x] 3.1 Verify rc-file discovery, loading, and skip-flag behavior end-to-end - Scenario: a valid rc file's settings/context take effect in both a piped run and an interactive run - Scenario: the skip flag leaves default settings untouched even with a valid rc file present - Observable: an integration test run demonstrates both scenarios pass against the real built binary - _Requirements: 1.1, 1.2, 1.4, 1.5, 2.1, 2.3_ - _Depends: 2.2_ - [x] 3.2 Verify rc-file failure handling matches the session's existing abort/continue behavior end-to-end - Scenario: a failing rc-file command in scripted/piped mode aborts before any of the piped script's own commands run, and the process exits with a failure status - Scenario: the same failing rc-file command in an interactive session still reaches the normal prompt afterward - Observable: an integration test run demonstrates both scenarios pass against the real built binary - _Requirements: 3.1, 3.2, 3.3, 3.4_ - _Depends: 2.2_ - [x] 3.3 Confirm existing xce_test automation is unaffected when no rc file is present - Run the existing HA soak and resource-verification scripts (the known live callers of xce_test today) in an environment with no rc file at the resolved location - Compare their pass/fail result against their pre-feature baseline behavior - Observable: the existing scripts' outcomes are unchanged, demonstrating this feature introduces no regression for callers that know nothing about it - _Requirements: 1.3_ - _Depends: 2.2_ ## Implementation Notes - Task 1.1: `controller_unit_test` can silently die with exit code 1/2 before reaching later-registered test groups if `/tmp` fills up — a pre-existing, already-tracked leak (bug-924) where test fixtures under `/tmp/xce_*` aren't cleaned up across many prior sessions' runs can exhaust disk and make `mkdtemp()` in unrelated tests (e.g. `test_repl_apply.c`) `exit(1)` on `ENOSPC`. If a later task's `controller_unit_test` run dies early or shows an unexplained failure in an unrelated file, check `df -h /tmp` before assuming a regression in this task's own code. - Task 1.1: the 2 pre-existing `test_config_repl.c` failures (`test_conf_trim_keeps_only_n_versions`, `test_conf_get_saved_tag_no_config`) are the tracked `bug-1561` overlayfs-artifact regression, unrelated to this spec — expect them to keep appearing in every subsequent task's `controller_unit_test` run; do not treat them as a new regression. - Task 1.2: `fopen()` succeeds on a directory path on this platform's glibc — the EISDIR failure only surfaces on the first `getline()` call (`errno=EISDIR`, `ferror(f)` set). A naive `if (!f)` open-failure check misses this; a post-loop `ferror(f)` check is required to catch it (see `rcfile_load()`, bug-1716). - Task 1.2: an ASAN heap-use-after-free was observed ONCE in an ad-hoc standalone `controller_unit_test` run (`commit_resolve_pending`/`fsync_worker`/`tap_teardown` in `test_apply_pump.c`) — did not reproduce on a second run or in the canonical `xce-build.sh` run. Unrelated to this spec's files; logged as bug-1717 (unreproduced flake) for tracking. If it recurs on a later task's run, capture the full ASAN report before assuming it's this spec's fault. - Task 3.1: `test_rcfile_integration.sh`'s per-test scratch `$HOME` dirs are cleaned via explicit `rm -rf` in each test function's normal flow, but there's no script-level `trap ... EXIT INT TERM` (unlike `clean_failover_preservation.sh`'s global trap) — an interrupted run could leak a small scratch dir. Verified low-impact (small footprint, no leak in normal runs) but worth adding a trap if this script is extended later. - Task 3.3: `xce-build.sh ` without `--incremental` wipes the whole build dir before rebuilding only that one component (documented bug-1565 behavior) — running it for a component other than `controller` after a controller build silently deletes `xce_controller`/`xce_test`. Use `--incremental` (or `all`) when you need multiple binaries from the same build dir alongside each other.