mergedhumanPR #184Negotiation
distributed-sys-engg: deterministic negotiation session ids (ADR-004 sibling fix)
Persona: distributed-sys-engg — reliability/determinism focus.
Author
@rakshit-tresata
github profile →- Status
- Merged
- Merged on
- Jul 10
- Branch
- hackathon/distributed-sys-engg-deterministic-negotiation-ids
Description
The pitch.
# Deterministic negotiation session ids (ADR-004 fix for `alternating_offers`)
**Persona:** `distributed-sys-engg` — reliability/determinism focus.
Sibling to the coordination-layer round-id PR (requested by the reviewer there:
"send it scoped to the negotiation layer the same way"). Same bug, same fix,
different layer.
## Motivation
`ADR-004-seeded-determinism` requires that the same seed replay to an identical
trace, so operators can diff two runs or replay a failure. The alternating-offers
plugin breaks it: `open()` mints its `NegotiationSession` id with
`uuid.uuid4()`, which draws from `os.urandom` and is unseedable. Two runs of the
same seed produce different session ids on every opened negotiation — the
plugin's public output is not reproducible.
Stated plainly: this is a latent bug today. No shipped scenario serializes the
session id into its trace, which is why it's gone unnoticed. But `open()` is a
public API with nondeterministic output, and any negotiation scenario or
validator that starts using session ids directly would silently inherit a trace
that can't be diffed or replayed.
## The change
Add `negotiation/_ids.py::derive_session_id(initiator, partner, seq)` and use it
in `open()`. The id is:
- Deterministic: a pure function of `(initiator, partner, monotonic
per-initiator sequence)`. A seeded run replays byte-for-byte.
- Injective on its inputs: the pre-image is a length-prefixed (netstring-style)
encoding, so distinct triples can't alias onto one digest through boundary
ambiguity (`("ab","c")` vs `("a","bc")`).
- Free of ambient state: no `uuid4`, no wall clock, no object identity, no
dict-ordering dependence.
`AlternatingOffers` keeps a private `_session_seq` counter. The sim drives
`open()` in a seeded, deterministic order, so the counter, and thus the id,
reproduces. Mirrors `coordination/_ids.py` exactly.
## Tests — fail without the change, pass with it
`packages/nest-plugins-reference/tests/test_negotiation_determinism.py`:
- Replay identity: opening the same negotiation sequence twice yields
byte-identical ids. This is the check that fails against `uuid4`.
- Uniqueness: successive sessions, distinct partners, and distinct initiators
all still get distinct ids (no functional regression).
- Derivation properties: purity, no ambient state, injective encoding, and a
Hypothesis property over arbitrary inputs.
## Verification
Local: `test_negotiation_determinism` 8/8 pass; the wider negotiation/market
suites (`test_pareto_negotiation`, `test_plugins`, `test_multi_attribute_market`,
`test_hotstuff_properties`) all pass unchanged (66 total), confirming no
regression. Please run `make ci-local` for the full gate.
Try it
Open PR on GitHubView diffCheckout locally
git fetch origin pull/184/head:pr-184
git checkout pr-184