myxce/.kiro/specs/libxce-alloc-preason-equality/tasks.md
Rongsong Shen dcf54387ae
docs(spec): libxce-alloc-preason-equality review fixes; new scheduler-sim-discrete-clock spec
libxce-alloc-preason-equality (requirements, design, tasks; not yet approved):
- alloc_eq compares `from` and excludes only instid; the scheduler stamps
  `from` with the deciding session's start, the same clock reading that
  is recorded as beginRound, so a replay reproduces it.
- every comparator takes an optional char** why: a malloc'd description
  of the first difference, owned and freed by the caller; --sim-verify
  mismatch lines print it (resolves bug-1881).
- job_preason_eq is new full-match behavior, not a relocation (bug-1845
  keeps the simulator main-reason-only); the three white-box test files
  switch to it and keep their sim_verify.c include.
- alloc_eq stays separate from merge_job_alloc()'s same_alloc_slot()
  (bug-1863), which does match on instid.

scheduler-sim-discrete-clock (initialized): remove --sim-speed so the
simulator clock moves only at recorded timestamps and a replayed
session_start equals the recorded beginRound exactly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-28 08:50:07 +00:00

8.4 KiB
Raw Blame History

Implementation Plan

Task Format

Work is sequenced test-first (TDD: red → green → refactor). New library API gets its unit tests first; the from stamping change gets a failing scheduler test first; the scheduler-side migration is then verified by the existing and extended --sim-verify tests.


  • 1. Add a shared NULL-safe string helper to libxce (utils.c/utils.h)

    • Add int str_eq(const char* a, const char* b) beside the existing float_equal helper: returns non-zero iff both arguments are NULL, or both non-NULL and strcmp(a, b) == 0.
    • Test it in test_preason.c or a utils test: NULL/NULL, NULL/non-NULL, equal, different.
    • Requirements: 1.5, 2.3 (supporting)
    • Boundary: utils.c, utils.h
  • 2. Allocation equality API with first-difference description (xcealloc.c/xcealloc.h), test-first

    • RED: in test_xcealloc.c, add tests for alloc_eq/host_alloc_eq/job_alloc_eq: match; mismatch per field (resname, reserve, value, duration, from) with the expected description in *why (freed by the test); instid difference still equal; host name, order and list-length sensitivity; jobid, ordered hosts, ordered shared, length mismatch; NULL at top level, nested entries and string fields; NULL why; *why == NULL on equal. In a dedicated test binary with -Wl,--wrap=vasprintf (bug-1843's --wrap idiom), a failed description allocation leaves *why NULL with the same result.
    • GREEN: implement the three functions with signature (a, b, char** why), comparing in the order given in design.md Data Models, value with exact == (not float_equal), strings via str_eq, descriptions via vasprintf in the design's path format (jobid via fmt_jobid, value with %.17g); a nested call's description is prefixed with the parent's path segment and the inner string freed.
    • Declare them in src/include/xcealloc.h with a doc comment covering: "same allocation decision" equality, from compared and instid excluded (and why), the difference from merge_job_alloc()'s slot matching, NULL/order semantics, the malformed-record precondition, and the why ownership contract (caller frees with free(); *why NULL on equal or on allocation failure).
    • Do not change make_alloc(), merge_job_alloc() or same_alloc_slot(); confirm existing test_xcealloc.c tests still pass.
    • Requirements: 1.1–1.7, 3.1–3.5, 6.4, 6.6
    • Boundary: xcealloc.c, xcealloc.h, test_xcealloc.c
    • Depends: 1
  • 3. Pending-reason equality API with description (preason.c/preason.h), test-first

    • RED: in test_preason.c, add tests for job_preason_eq: match; main-reason mismatch; host-based list mismatch per field, order and length; resource-based list mismatch per field, order and length; NULL handling; each with the expected description (freed by the test); NULL why; *why == NULL on equal.
    • GREEN: implement job_preason_eq(a, b, char** why) as a full structural match with the shared NULL convention.
    • Declare it in src/include/preason.h with a doc comment covering the full-match contract (nothing excluded), NULL/order semantics, malformed-record precondition, the why ownership contract, and that the simulator deliberately uses a main-reason-only check instead (bug-1845).
    • Requirements: 2.1–2.3, 3.1–3.5, 6.4
    • Boundary: preason.c, preason.h, test_preason.c
    • Depends: 1
  • 4. Reproducible from: one session-start reading, stamped on every allocation, test-first

    • RED: scheduler test(s) asserting (a) every allocation produced in one normal-mode session, including a COMPOUND request merged by merge_job_alloc(), has from == session_start / 1000000000; (b) with recording on, the recorded beginRound == session_start.
    • GREEN:
      • driver.c: pass sched->core.session_start to sched_record_session_begin() instead of a second xce_core_now() read.
      • scheduler.c:1893: after make_alloc(), set allocs[i]->from = sched->core.session_start / 1000000000ULL.
    • Check there is no other scheduler path that builds an xce_alloc_t for a decision outside a session; if one exists, stop and raise it (Revalidation Trigger).
    • Simulation side (Requirement 4.3) is spec scheduler-sim-discrete-clock; once it lands, add a test that a replayed allocation's from equals the recorded one.
    • Requirements: 4.1, 4.2, 4.4, 4.5, 6.5
    • Boundary: driver.c, scheduler.c, new/extended scheduler test
  • 5. Migrate the simulator to the public API and put the difference in the mismatch log (sim_verify.c, sim_loop.c)

    • Delete the five static comparators (sim_verify_str_eq, sim_verify_alloc_eq, sim_verify_host_alloc_eq, sim_verify_job_alloc_eq, sim_verify_job_preason_eq) and their from-exclusion comments.
    • Add a static simulator-local sim_verify_pend_outcome_eq(a, b, char** why) comparing main_reason only, documented with the bug-1845 rationale.
    • Change sim_decision_eq to (expected, actual, char** why): DISPATCH → job_alloc_eq(..., why); PEND → jobid equality plus the local main-reason check, describing whichever differs; kind mismatch described too. Update sim_loop.c's extern declaration and call.
    • sim_log_mismatch: get the description from sim_decision_eq, append -- diff: <why> to the existing line, then free() it (resolves bug-1881).
    • Add #include "xcealloc.h"; keep the describe_* helpers.
    • Requirements: 5.1, 5.2, 5.3
    • Boundary: sim_verify.c, sim_loop.c
    • Depends: 2, 4
  • 6. Migrate the three white-box test consumers to the public API

    • test_sim_equivalence.c, test_repl_equivalence.c, test_repl_limit_table.c: replace sim_verify_job_alloc_eq → job_alloc_eq and sim_verify_job_preason_eq → job_preason_eq (take the description, print it in the failure message and free it, or pass NULL); add the two headers.
    • Keep each file's white-box #include "../src/sim_verify.c": the files also include sim_loop.c, which calls sim_decision_eq/sim_log_mismatch.
    • The switch to full-match job_preason_eq is valid because none of these tests registers unsatisfiable_lookup_cb, so both sides build detail lists by the same full per-host computation; add a short comment at each job_preason_eq call stating this. from is now compared too. If a test fails after either change, investigate it as a real divergence; do not loosen the comparison.
    • Update comments that name sim_verify_*_eq or describe from as excluded in these files and in test_sim_live_allocation.c (~702, ~718) and test_sim_reconcile_unaccounted.c (~550).
    • Requirements: 6.1, 6.2, 6.3, 6.7
    • Boundary: test_sim_equivalence.c, test_repl_equivalence.c, test_repl_limit_table.c, test_sim_live_allocation.c, test_sim_reconcile_unaccounted.c
    • Depends: 2, 3, 4, 5
  • 7. Reconcile test_sim_verify.c with the relocated comparators

    • Remove the DISPATCH alloc-level unit tests that only re-prove the allocation comparators — now owned by libxce tests from task 2. Replace the "from difference is ignored" test with one that shows same-session allocations verify cleanly and a changed from is reported as a mismatch naming from.
    • Keep the PEND unit tests (identical-content match, jobid/main-reason mismatch, detail-ignored) — they pin sim_decision_eq's simulator-local main-reason-only behavior.
    • Add a test that sim_log_mismatch's line contains the differing field and both values.
    • Run the --sim-verify round-trip tests (zero-mismatch self-replay, altered replay, reconcile cases). Until scheduler-sim-discrete-clock lands, at --sim-speed 0.
    • Requirements: 5.2, 5.3, 5.4, 6.4
    • Boundary: test_sim_verify.c
    • Depends: 5
  • 8. Full build, test and live verification

    • /xce-build lib (debug + ASAN + tests) green.
    • /xce-build scheduler --incremental (debug + ASAN + tests) green, including test_sim_verify.c, test_sim_equivalence.c, test_repl_equivalence.c, test_repl_limit_table.c.
    • /xce-build controller --incremental green (consumes xcealloc.h; confirms nothing else depended on the removed names).
    • Live (after scheduler-sim-discrete-clock): record a session on a local deployment, replay it with --sim-verify: zero mismatches.
    • grep -rn 'sim_verify_\(host_\|job_\)\?\(alloc\|preason\)_eq' src returns no matches.
    • After merge: buglog.py status bug-1881 fixed --commit <sha> with the Verification section filled in.
    • Requirements: 5.4, 6.6, 6.7
    • Depends: 2, 3, 4, 5, 6, 7