# 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`.