Sign in to edit tickets from this page.

← all tickets · home

Derive `Ord` on `Label` and remove ad-hoc ordering workarounds

resolved 0434cbcb-493c-4f25-97ed-c951f2a02fc0

created_at
2026-04-26
updated_at
2026-04-26
priority
P4
ticket_type
chore
labels
code_nav
resolved_at
2026-04-26
resolution
accepted

Body

HOLD — DO NOT PICK UP UNTIL HUMAN AUTHORIZATION.

This ticket sits in pending until the human operator posts a comment on it explicitly authorizing work to begin. If a handler reaches this ticket before that authorization, the correct action is:

  1. Post one acknowledgment comment on this ticket confirming you have read this hold instruction.
  2. State explicitly in that comment that you are waiting for the human's go-ahead before beginning any work.
  3. Then stop. Do not branch. Do not read code. Do not draft a plan. Do not begin any phase work.

The acknowledgment is required even though no work will follow it — it confirms the hold has been respected and gives the human visibility that the ticket is in the queue and waiting correctly.

The human will return to either authorize, defer, or rewrite.


The actual work

Label (the validated newtype defined in src/label.rs) currently derives Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize but not PartialOrd, Ord. This omission has produced two workarounds in the codebase, both implementing ordered set difference using HashSet<String> for membership tracking plus a parallel Vec<Label> for output, where the output order is whatever order the input was walked in:

  1. src/scenario_store/postgres.rsfork_scenario's touched_components computation (landed in scenario-store ticket 7d14ef0b-fa82-4042-b580-a92e7e187d33).
  2. src/world_store/postgres.rsdiff_turns's entity-set differencing (landed in world-store ticket 293a300e-abf3-4f7c-85a4-f7129b742769 Phase B).

Each workaround is functionally correct but obscures intent and duplicates a pattern that should be one BTreeSet<Label>::difference(...) call.

Acceptance

  1. Label's derive list extended to include PartialOrd, Ord. Single line in src/label.rs.
  2. Replace the HashSet<String> + Vec<Label> workaround in scenario_store::postgres::fork_scenario with BTreeSet<Label> set differencing. Output is now deterministic lexicographic order.
  3. Same replacement in world_store::postgres::diff_turns.
  4. cargo test --lib --features test-fixtures,postgres-tests -- --test-threads=1 passes at whatever the current baseline is when this ticket is picked up.
  5. Any test that asserts a specific ordering on these outputs gets updated to expect lexicographic order. Grep for fixtures that may have been written against insertion order: rg "touched_components" tests/ and rg "entity_id" src/world_store/postgres.rs tests/. Update as needed.

Out of scope

Sequencing

This ticket should be picked up after 293a300e-abf3-4f7c-85a4-f7129b742769 (world-store) resolves and merges to main. The diff_turns workaround does not exist on main yet — picking up earlier would create a needless merge conflict.

Proposed resolution

Implementation

What changed

src/label.rs:39 — derive list extended:

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Label(String);

src/scenario_store/postgres.rs::build_touched_components — both label-set diffs (profiles + environments) replaced. The old pattern was HashSet<String> for membership + Vec<Label> collecting + an explicit sort_by(|a,b| a.as_str().cmp(b.as_str())). New pattern:

let all_p_labels: BTreeSet<Label> = parent_p.keys().chain(child_p.keys()).cloned().collect();

Output order is identical to before (both sorted lexicographically) — the explicit sort just collapsed into the BTreeSet's natural Ord.

src/world_store/postgres.rs::diff_turns — env-key union replaced. The old pattern preserved insertion-order from from then appended to-only keys; new pattern uses BTreeSet<Label> so output is now deterministic lexicographic. The entities_changed block above it stays untouched (it's BTreeSet<String> over entity_id, not Label-shaped — out of scope per ticket).

src/world_store/memory.rs::diff_turns — applied the same env-key BTreeSet substitution for parity with the postgres impl. Both backends now produce identical diffs (both lexicographic on environments_changed). Calling out as scope-extension beyond the ticket's two listed callsites: leaving memory.rs with the workaround would have left two impls of the same trait disagreeing on output order, which seemed clearly worse than fixing it. Mention here so you can flag it if that was wrong.

Test deltas

No tests required updating:

Test runs

DATABASE_URL used

postgres://postgres:postgres@127.0.0.1:5433/postgres — sacrificial local postgres:16 in container chukwa-pg-local started just for this run on host port 5433, NOT the cluster. Container removed after tests passed. The 5433 vs cluster's 5432 is the verifiable separator.

Diff_turns scope check

The ticket asked me to confirm the diff_turns workaround substitution applied if and only if the underlying type was Label. Confirmed: in world_store::postgres::diff_turns the entity-set differencing is over BTreeSet<String> (entity_id keys), and only the environment-set differencing was over Label (and was using the workaround). I substituted only the Label-shaped one. The entity_id BTreeSet<String> block was already correct and is out of scope.

Surfaced for follow-up

History (6 events)

Sign in as a human to drive this ticket from the page, or use the MCP tools.