Add test_sim_replay_reproduces_recorded_alloc_from: a recorded DISPATCH at an epoch-scale beginRound with a sub-second part replays at the default simulation settings with no --sim-verify mismatch, and the live allocation's from equals beginRound in whole seconds. Share the dispatch fixture setup of the existing tests through two helpers. Live: 2-round HA soak recordings (11 DISPATCH decisions) replayed with --sim-verify and no --sim-speed report 0 mismatches. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
12 KiB
Implementation Plan
Test-first throughout (red → green → refactor). Every sub-task leaves the build and all test suites green: consumers move to the public API before the private comparators are deleted, test fixtures are brought in line before from starts being compared, and each change updates the tests it affects.
-
1. Foundation: shared NULL-safe string equality
-
1.1 Provide a NULL-safe string-equality helper in libxce
- Write the tests first: two NULL strings are equal; one NULL and one non-NULL are not equal in either order; equal strings are equal; different strings are not.
- Add the helper beside the existing floating-point comparison helper in the shared utilities, as described in design.md (Decision 5).
- Done when the libxce utility tests include these cases and the libxce suite passes under ASAN.
- Requirements: 1.5, 2.3
-
2. Core: public equality API in libxce
-
2.1 Allocation equality for single records, host allocations and job allocations, with an optional first-difference description
- Write the tests first: a match; a mismatch on each compared field (resource name, reserve, value, duration, reservation start time) with the expected description; an allocation-instance-id difference still compares equal; host name, entry order and list length matter; job id, ordered hosts, ordered shared resources and list-length mismatches; NULL handling at the top level, in list entries and in string fields; no description requested; no description returned when equal.
- Add a wrapped description-allocation failure to the existing allocation OOM test binary (bug-1882) and test that a failed description leaves no description and the same result.
- Implement the three comparisons in the allocation module with the field order, exact value comparison, description path format and ownership contract of design.md (Data Models, Components: Allocation equality, Decision 9), enabling the GNU extension the description formatter needs.
- Document in the public header: "same allocation decision" equality, reservation start time compared and instance id excluded (and why), the difference from the merge helper's slot matching, NULL/order semantics, the malformed-record precondition, and that the caller frees the description.
- Leave the allocation constructors and the merge helper unchanged.
- Done when the new tests pass, the existing allocation and allocation-OOM tests still pass, and every test frees the descriptions it receives (clean under ASAN/LeakSanitizer).
- Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 6.4, 6.6
- Boundary: libxce allocation module
-
2.2 Full-match pending-reason equality with an optional first-difference description
- Write the tests first: a match; main-reason mismatch; host-based reason list mismatch per field, order and length; resource-based reason list mismatch per field, order and length; NULL handling at the top level, in list entries and in string fields; each with the expected description; no description requested; no description returned when equal; a failed description allocation (in the allocation OOM test binary, which already wraps the formatter after 2.1) leaves no description and the same result.
- Implement the full structural match in the pending-reason module per design.md (Components: Pending-reason equality, Decision 3), enabling the GNU extension the description formatter needs.
- Document in the public header the full-match contract, NULL/order semantics, the malformed-record precondition, the description ownership, and that the simulator deliberately uses a main-reason-only check instead (bug-1845).
- Done when the new tests pass and the libxce suite passes under ASAN with leak detection.
- Requirements: 2.1, 2.2, 2.3, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 6.4
- Boundary: libxce pending-reason module; the allocation OOM test binary (shared test harness, sequenced after 2.1)
- Depends: 2.1
-
3. Core: reproducible reservation start time in the scheduler
-
3.1 (P) Record each session's start from the same clock reading as the session start
- Write the test first, in its own test function (not shared with 3.2): with recording enabled, a recorded session's begin time equals the session's start time exactly.
- Take the recorded begin time from the session start instead of a second clock reading (design.md, Components: Recording).
- Done when the test passes and the existing recording tests still pass.
- Requirements: 4.2, 6.5
- Boundary: scheduler normal-mode driver
-
3.2 (P) Stamp every allocation with its deciding session's start time, from the session core
- Write the tests first, in test functions separate from 3.1's: every allocation of one normal-mode session, including every primary of a merged COMPOUND request, carries the session start in whole seconds; the same holds in simulation mode, where the allocators' scheduler handle is the simulator's session-less wrapper, using a realistic epoch-scale recorded session start so a stamp taken from the wrong core (0) fails.
- Stamp the reservation start time on every per-host and shared allocation record where the allocator result is received with the session core, not in the record-building helper (design.md, Components:
fromstamping, Decision 8). - Confirm no allocation path bypasses that point; if one does, stop and raise it.
- The simulator still ignores the reservation start time at this point, so no fixture changes are needed yet.
- Done when the new tests pass and the full scheduler suite passes.
- Requirements: 4.1, 4.4, 4.5, 6.5, 6.6
- Boundary: scheduler allocation chain
-
4. Integration: move the consumers to the public API
-
4.1 Move the three white-box equivalence tests to the public comparators
- Switch the three test files that call the simulator's private comparators to the public allocation and full-match pending-reason comparisons, printing the description in their failure messages and freeing it.
- Keep their white-box inclusion of the simulator's verification source, which they still need for the decision-level functions.
- At each full-match pending-reason call, add a short comment on why full match is valid there (no unsatisfiable-allocation cache on either side).
- Update their header comments that name the private comparators.
- Done when the three tests pass unchanged in outcome under the stricter checks; a failure is investigated as a real divergence, not loosened.
- Requirements: 6.1, 6.2, 6.3, 6.7
- Depends: 2.1, 2.2, 3.1, 3.2 (3.1 as well as 3.2: a normal-mode recording's begin time and its allocations' start time must come from one clock reading, or a recorded-vs-replayed comparison can differ by one second)
-
4.2 Bring hand-built recorded dispatch fixtures in line with the session start time
- In every test that hand-builds a recorded dispatch later compared against a live replay, set the recorded allocation's reservation start time to the fixture session's recorded start in whole seconds (design.md, File Structure Plan: the six listed test files).
- Update the comments in those files that describe the reservation start time as excluded or name the private comparators.
- Harmless before 4.3, because the simulator still ignores the field; required after it.
- Done when every listed fixture carries the session-start value and the scheduler suite still passes.
- Requirements: 6.2, 6.7
- Depends: 3.2
-
4.3 Migrate simulator verification to the public API and report the difference in mismatch logs
- Write the tests first: add the new description argument to the verification unit test's direct decision-comparison calls; test that the description names the differing field and both values for a dispatch difference, a pending job-id difference, a pending main-reason difference, a kind difference, a missing side and an unsupported preempt; test that the mismatch log line appends a provided description; invert the "reservation start time difference still matches" test so a difference is now reported and named.
- Delete the simulator's private comparators; compare dispatch decisions with the public allocation comparison; keep a simulator-local main-reason-only pending check with the bug-1845 rationale (design.md, Components: Simulator verification).
- In the simulator's replay loop (the only code caller of the decision comparison and the mismatch log), pass the description it already computes to the mismatch log and free it, and pass a fixed "no live decision" text for recorded decisions left over; the signature changes and these callers land in this same sub-task.
- Done when the verification tests and the self-replay, altered-replay and reconcile round trips pass (they already run with the simulator clock frozen), and every mismatch line carries a diff.
- Requirements: 5.1, 5.2, 5.3, 5.4, 6.1, 6.4
- Depends: 2.1, 3.1, 4.1, 4.2
-
5. Validation
-
5.1 Remove duplicated coverage from the verification unit tests
- Remove the dispatch allocation-level tests that only re-prove the allocation comparisons now owned by libxce; keep the pending-decision, decision-level and integration tests, the inverted reservation-start-time test from 4.3, and the description-text tests from 4.3.
- Done when the verification test file no longer duplicates libxce coverage and still passes.
- Requirements: 6.1, 6.4
-
5.2 Full build and regression
- Run the full ASAN build with the libxce, scheduler and controller suites.
- Confirm no source still names any of the five removed private comparators (the string, allocation, host-allocation, job-allocation and pending-reason ones); the decision-level comparison keeps its name.
- Done when every suite passes and the search finds no match.
- Requirements: 5.4, 6.6, 6.7
-
5.3 Replay reproduces the reservation start time (after spec scheduler-sim-discrete-clock)
- Add a test that a replayed allocation's reservation start time equals the recorded one at the default simulation settings.
- Record a session on a local deployment and replay it with verification at the default settings.
- Done when the test passes and the live replay reports zero mismatches; then mark bug-1881 fixed through the buglog skill with the fix commit.
- Requirements: 4.3, 5.4
- Depends: spec scheduler-sim-discrete-clock
Implementation Notes
xce-build.shresolves a relative build dir undersrc/:bash src/tools/xce-build.sh lib asan xce.build.alloceqbuilds intosrc/xce.build.alloceq, so iterate withmeson test -C src/xce.build.alloceq.xce-build.sh libruns only thelibxce testbinary; runmeson test -C src/xce.build.alloceq "libxce alloc OOM test"(or plainmeson test) for the allocation OOM binary, which also wrapsvasprintf/__vasprintf_chksince 2.1.- Simulation mode (
sim_loop.c) still takes its own session-begin clock reading beforexce_core_begin_session(); by design that is left to spec scheduler-sim-discrete-clock (3.1 fixed only the normal-mode recorder). UPDATE 2026-09-29: spec scheduler-sim-discrete-clock is implemented (commits 4f653d8f..d9bc36cd; validation GO): the simulator clock is pinned, and a replayedsession_startequals the recordedbeginRoundexactly at default settings, so 5.3 is unblocked. - 4.2 left the
from-excluded comments attest_sim_verify.c~639-681 for 4.3, which inverts the test they document. sim_verify.cbuilds descriptions withvsnprintf+malloc, notvasprintf: about 25 scheduler tests#include "../src/sim_verify.c"after<stdio.h>without_GNU_SOURCE. Same ownership contract as libxce.- 5.2:
xce-build.sh allstops at the xceadmin live-HA e2e test (test_live_config_push_against_primary_reaches_success_path, versionTag 0); it uses controller binaries from the shared default build dirs, not this build. The spec needs only lib/scheduler/controller: built with--incrementalper component, thenmeson test --no-suite xceadmingave 116/116, 0 Unity :FAIL lines, 0 sanitizer reports. - 5.3:
test_sim_replay_reproduces_recorded_alloc_from(test_sim_live_allocation.c) uses an epoch-scale beginRound with a 0.75 s fraction; the olderbegin_round = 2000fixtures cannot tell a correctfromfrom 0 or from sub-second drift (a +0.5 s clock mutant in sim_loop.c fails only the new test). Standalonexce_scheduler --simexits 1 under ASAN from exit-time LeakSanitizer reports (snapshot decode), so live replays needASAN_OPTIONS=detect_leaks=0, as the HA soak's replay gate already sets. Live proof: 2-round HA soak (/home/claude/tmp/xce-alloceq-53), 3 clean recordings, 11 recorded DISPATCH decisions, replayed with--sim-verifyand no--sim-speed: 0 mismatches.