Skip to content

test(agent): pin LU-11 random-sampling prover backfill orchestration#743

Merged
branarakic merged 5 commits into
release/rc.12from
test/rc12-lu11-backfill-orchestration
May 27, 2026
Merged

test(agent): pin LU-11 random-sampling prover backfill orchestration#743
branarakic merged 5 commits into
release/rc.12from
test/rc12-lu11-backfill-orchestration

Conversation

@branarakic

Copy link
Copy Markdown
Contributor

Summary

PR #716 audit cluster B.4 — closes the last LU-11 coverage gap:
the buildCiphertextChunkBackfill closure that glues the
Random Sampling prover to fetchCiphertextChunkFromPeer.

Background

dkg-agent.ts:11221-11299. The closure is handed to the prover
via bindRandomSampling (dkg-agent.ts:2832-2870) and invoked
whenever the prover's extractor reports a
CiphertextChunksMissingError (OT-RFC-39 late-join path — this
core missed the curator's chunked publish).

Per-building-block coverage existed:

But the orchestration layer — peer iteration, self-exclusion,
denied/error classification, aggregation — had zero direct
coverage. A refactor that flipped `continue`/`break` semantics or
dropped the self-filter would silently break the late-join
contract on every core that joined a CG after the curator's
chunked publish rolled off the gossip mesh.

What this PR adds

New file
`packages/agent/test/lu11-backfill-orchestration.test.ts` — 9
tests covering all 6 return shapes + 3 iteration policies.

Return shapes (6 tests)

  • `{ fetched: N, failures: 0 }` — happy path, all chunks land
  • `{ reason: 'cg-not-locally-registered' }` — chain-event race window
  • `{ reason: 'no-peers' }` — workspace topic empty after self-exclusion
  • mixed `fetched/failures` with NO aggregated reason
  • `{ reason: 'all-denied: ' }` — every peer authoritatively no
  • `{ reason: 'no-responders' }` — every peer transport-failed (no ACK)

Iteration policies (3 tests)

  • Self-exclusion — local peerId is filtered out of candidates
    even when the local node subscribes to its own workspace topic.
    Without this the prover would try to fetch its own missing
    chunks from itself — guaranteed failure mode + wasted candidate
    slot.
  • Per-chunk fall-through — a `denied` ACK from peer A advances
    to peer B for the SAME chunk; a successful B short-circuits the
    inner loop. Pins both halves of the loop semantics.
  • Zero missing indexes — fast short-circuit BEFORE topic
    resolution; neither `gossip.getSubscribers` nor the messenger is
    invoked.

The orchestrator is correct today (verified by inspection); these
pins make peer-iteration / aggregation drift loud at PR time.

Test plan

  • `pnpm exec vitest run test/lu11-backfill-orchestration.test.ts` — 9 passed
  • No lints

Related: #716, #715, #717, #727, #729, #742.

Made with Cursor

PR #716 audit cluster B.4 — `buildCiphertextChunkBackfill` at
`dkg-agent.ts:11221-11299` is the curated-path glue between the
Random Sampling prover and `fetchCiphertextChunkFromPeer`. The
prover invokes the returned closure whenever its extractor reports
a `CiphertextChunksMissingError` (the OT-RFC-39 late-join path).

The closure's individual building blocks all had direct coverage:
  - `fetchCiphertextChunkFromPeer` (#742)
  - `handleGetCiphertextChunk` (#739)
  - `ingestSwmCiphertextChunkEnvelope` (#742)

But the ORCHESTRATION layer — peer iteration, self-exclusion,
denied/error classification, aggregation — had zero direct
coverage. A refactor that flipped continue/break semantics or
dropped the self-filter would silently break the late-join
contract on every core that joined a CG after the curator's
chunked publish rolled off the gossip mesh.

New file `packages/agent/test/lu11-backfill-orchestration.test.ts`
pins all 6 return shapes + 3 iteration policies, 9 tests total:

Return shapes:
  - `{ fetched: N, failures: 0 }` happy path
  - `{ reason: 'cg-not-locally-registered' }` chain-event race window
  - `{ reason: 'no-peers' }` workspace topic empty after self-exclusion
  - mixed `fetched/failures` with NO aggregated reason
  - `{ reason: 'all-denied: <lastDenied>' }` everyone authoritatively no
  - `{ reason: 'no-responders' }` everyone transport-failed (no ACK)

Iteration policies:
  - Self-exclusion: local peerId is filtered out even when the
    local node subscribes to its own workspace topic. Without
    this the prover would try to fetch its own missing chunks
    from itself, guaranteed failure mode + wasted candidate slot.
  - Per-chunk fall-through: a `denied` ACK from peer A advances
    to peer B for the SAME chunk; a successful B short-circuits
    the inner loop. Pins both halves of the loop semantics.
  - Zero missing indexes: fast short-circuit BEFORE topic
    resolution — neither `gossip.getSubscribers` nor the messenger
    is invoked.

The orchestrator is correct today (verified by inspection); these
pins ensure peer-iteration / aggregation semantics can't silently
drift on a future refactor.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
…ests

Three issues raised, all valid:

1. Do not replace `agent.node` with a bare `{peerId}` stub.
   `DKGAgent.stop()` reaches into `this.node.stop()` during teardown
   and the bare stub would silently break shutdown — masked by the
   `afterEach` swallow-catch, leaking timers/libp2p state into later
   tests. Override the agent's OWN `peerId` getter via
   `Object.defineProperty` instead, shadowing the prototype getter
   on the instance and leaving the real `node` intact.

2. `ReliableSendResult` union: `delivered: false` with `queued: false`
   is only valid for the `inFlight: true` sender-side dedup variant.
   Hard transport failures use `delivered: false, queued: true,
   nextAttemptAtMs` (durable retry). Switched both occurrences to
   the durable-retry shape — the realistic production failure mode
   and the one the union actually admits.

3. The `all-denied` test accepted either peer's denial reason, so a
   regression from "last-denial-wins" to "first-denial-wins" would
   pass silently. The closure iterates the candidate set in
   insertion order (Set-from-Array preserves it), so with
   `[peerA, peerB]` subscribers the FINAL `lastDenied` is always
   peerB's reason. Pinned the exact value.

Codex review on #743.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Codex review (round 2) on PR #743 raised two valid follow-ups:

1. The `afterEach` wrapped `agent.stop()` in `.catch(() => undefined)`,
   which would hide any teardown regression — for example a future
   reintroduction of the round-1 `node`-replacement bug would
   silently leak timers/libp2p state into later tests while the
   originating test stayed green. Removed the swallow: null the
   reference first (so the next test gets a fresh slot even if
   teardown throws), then await `stop()` without a catch. Teardown
   bugs now fail locally rather than in a downstream suite.

2. The messenger stub captured the protocol but never validated
   it, and ignored the payload entirely. A regression that called
   the wrong protocol id or sent the wrong (contextGraphId,
   batchId, chunkIndex) wire fields would slip through every
   test that used the helper. Extended the capture to include
   payload bytes, and added validation in the happy-path test:
     - protocol id === '/dkg/10.0.2/get-ciphertext-chunk'
     - decoded contextGraphId equals the local cg id
     - decoded batchId equals the requested batchId
     - decoded chunkIndexes match the missingIndexes in order

Codex review on #743 (round 2).

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Codex review (round 3) on PR #743 raised three valid follow-ups:

1. The all-denied and all-errored tests used a single
   `missingIndexes: [0]` entry, so they exercised only the
   single-chunk path of `lastDenied`/`failures` aggregation. A
   regression that miscounted across chunks would slip through.
   Both tests now use `missingIndexes: [0, 1]` and assert the
   exact `failures: 2` count. The all-denied test's "last-denial-
   wins" assertion now holds across BOTH chunks (peerB's reason
   is still the final value because peerA precedes it in
   iteration order for each chunk).

2. The happy-path wire-fidelity check hardcoded the protocol
   string `/dkg/10.0.2/get-ciphertext-chunk`. Routine protocol-
   version bumps would otherwise break the test even when the
   orchestrator's behavior is still correct. Imported and used
   `PROTOCOL_GET_CIPHERTEXT_CHUNK` from `dkg-core` instead, so the
   assertion only fails on real protocol changes.

3. Inline comments were noisy with review provenance
   (`Codex review (round N) feedback: ...`). Stripped the tool /
   round markers while keeping the behavioral rationale —
   provenance is in the commit log and PR history, not the test
   body.

Codex review on #743 (round 3).

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts
Comment thread packages/agent/test/lu11-backfill-orchestration.test.ts Outdated
Codex review (round 4) on PR #743 raised two valid follow-ups
on the all-denied test:

1. The test asserted only the final counters (fetched=0,
   failures=2). A regression that stopped after the first
   missing index and derived `failures` from
   `missingIndexes.length` would still pass — the counter would
   read 2 with only 2 wire requests instead of 4. Pinned the
   actual request pattern: 4 sends, peers cycle [A, B, A, B]
   across the two chunks, chunk indexes are [0, 0, 1, 1].

2. The exact-suffix pin (`'all-denied: peer-rate-limited'`)
   was over-constraining the impl: `result.reason` is documented
   as operator-facing free-form, so a harmless refactor (peer
   sorting, parallel fetches) would break the test without
   changing the contract. Loosened to assert that:
     - reason starts with `all-denied: ` (proves the aggregator
       surfaced a denial-class result, not a transport error or
       empty string)
     - the suffix is one of the two reasons wired into the stub
       (proves the suffix isn't empty/garbled)
   Which specific peer's reason wins is now an iteration-order
   detail, not a behavioral contract.

Resolves the round-2 / round-4 review contradiction: round 2
asked to pin the exact reason (to distinguish first-vs-last);
round 4 said that's too brittle. The per-chunk request pattern
already pins iteration order independently, so the reason
assertion can be loose without losing first-vs-last protection.

Codex review on #743 (round 4).

Co-authored-by: Cursor <cursoragent@cursor.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codex review completed — no issues found.

@branarakic branarakic merged commit 97d8ddc into release/rc.12 May 27, 2026
3 checks passed
@branarakic branarakic deleted the test/rc12-lu11-backfill-orchestration branch May 27, 2026 13:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant