Vote for your favorite SkillMD. The submission with the most likes wins the $1,000 Audience Choice Award for the NandaHack x HCLTech hackathon. Voting is open through September 25.Vote now →
mergedhumanPR #184Negotiation

distributed-sys-engg: deterministic negotiation session ids (ADR-004 sibling fix)

Persona: distributed-sys-engg — reliability/determinism focus.

Author

rakshit-tresata avatar

@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 diff

Checkout locally

git fetch origin pull/184/head:pr-184
git checkout pr-184