myxce/.kiro/specs/libxce-alloc-preason-equality/research.md
Rongsong Shen 6e0463e707
chore: persist OpenWolf state and libxce-alloc-preason-equality spec
- .wolf/anatomy*, memory.md: hook-maintained index and session log
  through the bug-1851..1854 work.
- .kiro/specs/libxce-alloc-preason-equality/: requirements, design,
  research and tasks generated; none approved yet (spec.json phase
  tasks-generated).

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

7.7 KiB

Research & Design Decisions

Summary

  • Feature: libxce-alloc-preason-equality
  • Discovery Scope: Extension (relocate existing comparison logic, add public API, migrate consumers)
  • Key Findings:
    1. The type-level equality comparators in sim_verify.c operate purely on libxce structs with zero scheduler/capnp/replay dependency — they are cleanly movable; the decision-level dispatcher and mismatch reporting are not.
    2. The comparators are already shared across four translation units, but via white-box #include "../src/sim_verify.c" + direct calls to static functions (test_sim_equivalence.c, test_repl_equivalence.c, test_repl_limit_table.c).
    3. The controller (persist.c, resync.c, job.c) already consumes xce_job_alloc_t/xce_host_alloc_t/xce_job_preason_t/xce_alloc_t with no equality API — confirming these structs are genuinely cross-component, not scheduler-only.
    4. One deliberate field exclusion is baked into the alloc comparators (from/instid), which generalizes cleanly into a shared identity contract. The preason comparator's main-reason-only narrowing is not an identity property — it is a replay artifact (bug-1845) that must stay in the simulator while the shared job_preason_eq becomes a full match.

Research Log

Where do the equality checks live?

  • Context: locate all comparison logic in the scheduler simulator.
  • Sources Consulted: src/scheduler/src/sim_verify.c (full read); src/scheduler/src/sim_loop.c, sim_alloc.c, sim_picker.c, sim_limit.c (grep for _eq/_equal/compare).
  • Findings: sim_verify.c (474 lines) is the only file with equality logic. It holds five static leaf comparators plus sim_decision_eq, sim_log_mismatch, and two describe_* helpers. No other sim file compares these structs.
  • Implications: the split point is exactly between the five static leaf functions and sim_decision_eq.

Who consumes these comparators, and how?

  • Context: understand the "future sharing" concern.
  • Sources Consulted: grep for sim_verify_job_alloc_eq/sim_verify_job_preason_eq/sim_verify_alloc_eq across src/scheduler/testsuite/.
  • Findings: three test files call the two leaf comparators directly after white-box-including sim_verify.c: test_sim_equivalence.c:894,902, test_repl_equivalence.c:734,742, test_repl_limit_table.c:2662,2670. Each builds its own replay_decision_content_eq-style helper on top of the two leaves.
  • Implications: moving the leaves to libxce turns a fragile include-and-call-static hack into a normal public API; these three files must migrate their call sites.

Are these structs shared beyond the scheduler?

  • Context: justify libxce as the destination rather than "scheduler convenience".
  • Sources Consulted: grep for the four struct names across src/controller, src/worker, src/harness, src/lib.
  • Findings: xce_job_alloc_t/xce_host_alloc_t/xce_job_preason_t/xce_alloc_t are consumed by src/controller/persist.c (Cap'n Proto encode/decode), src/controller/resync.c, and src/controller/job.c, plus the scheduler. No equality API exists anywhere.
  • Implications: the controller is a real future consumer of equality; the API belongs in libxce.

Where do the structs' construction/dup helpers already live?

  • Context: choose the destination module.
  • Sources Consulted: src/lib/xcealloc.c (read full), src/include/xcealloc.h, src/lib/preason.c, src/include/preason.h.
  • Findings: make_alloc/make_host_alloc/make_job_alloc/dup_job_alloc/make_job_account in xcealloc.c; erase_job_preason/add_job_preason/make_pendreason in preason.c. No existing _eq/_equal convention in libxce (only float_equal in utils.h); no NULL-safe string-equality helper exists.
  • Implications: co-location with construction is the natural home; the string helper is new and only needed by the alloc family today.

Architecture Pattern Evaluation

Option Description Strengths Risks / Limitations Notes
Co-locate in existing modules equality beside make_*/dup_*/erase_* matches precedent, no new module none material selected
New xce_equality.{c,h} one module for all four single discoverable location new module for 4 small functions rejected (YAGNI)
Leave in scheduler, expose via header promote the statics to scheduler-external minimal controller can't use them; wrong ownership rejected

Design Decisions

(Full decisions live in design.md; the significant trade-offs are recapped here.)

Decision: job_preason_eq is a full structural match; the simulator keeps its own main-reason-only PEND check

  • Context: sim_verify_job_preason_eq compares only main_reason, deliberately (bug-1845) — sim has no alloc cache, so its full detail can never match a recorded cache-hit's main-reason-only shape; requiring detail to match produced systematic false mismatches. That narrowing answers "same replay outcome", not "identical pending-reason objects".
  • Alternatives Considered:
    1. Publish full-deep job_preason_eq + keep a static main-reason-only helper in sim_verify.c for the PEND branch (selected).
    2. Publish job_preason_eq as main-reason-only (rejected — leaks a replay artifact into a general-purpose equality API).
  • Selected Approach: job_preason_eq(a, b) returns non-zero iff main_reason matches and both detail lists (host-based, resource-based) match in order field-for-field. sim_decision_eq's PEND branch uses a small simulator-local static main-reason-only comparison instead of job_preason_eq.
  • Rationale: a public "same pending reason" predicate must be identity-honest; the replay asymmetry is a scheduler-only concern and stays where it belongs.
  • Trade-offs: two PEND-comparison shapes exist (full-match in libxce, main-reason-only in sim_verify.c) — intentional, they answer different questions, and the sim-local helper is static and small.
  • Follow-up: none expected; the sim-local helper is not exported.

Decision: preserve NULL and order semantics exactly

  • Context: the current leaf comparators use "identical pointer (including both NULL) → equal; exactly one NULL → not-equal", and ordered list comparison.
  • Selected Approach: reproduce exactly in libxce.
  • Rationale: the relocation is required to be zero-behavioral-change (Requirement 3); ordered comparison is correct because both sides come from the same deterministic construction.

Risks & Mitigations

  • Risk: a consumer silently relies on the excluded fields (from/instid, preason detail) → Mitigation: explicit doc contract on each function + revalidation triggers; no behavior change for existing consumers.
  • Risk: migration of the three white-box test files breaks their replay_decision_content_eq helpers → Mitigation: mechanical call-site rename, verified by the full scheduler suite (task 7).
  • Risk: duplicate-symbol collisions if a test both white-box-includes sim_verify.c and links libxce after the statics are removed → Mitigation: the statics are deleted from sim_verify.c (task 4) before the test files migrate (task 5), so the names resolve only against libxce.

References

  • src/scheduler/src/sim_verify.c — current home of the comparators (source of truth for field lists and exclusion rationale).
  • src/include/xce/types.h — the four struct definitions.
  • src/lib/xcealloc.c, src/lib/preason.c — destination modules and their construction/dup conventions.
  • .kiro/specs/scheduler-snapshot-job-allocation/ — precedent for a scheduler-driven spec adding libxce helpers (dup_job_alloc) to xcealloc.c.