# Each message carries a monotonic sequence number enabling gap detection

- **Pattern:** `ab-000740` (`community-realtime.message-delivery.monotonic-sequence-numbers`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000740
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000740)

## Why it matters

Without monotonic sequence numbers, clients have no way to detect that messages were dropped during a network disruption. A user rejoining after a brief outage will see the conversation with silent gaps — no indication that messages are missing. CWE-354 (Improper Validation of Integrity Check Value) covers this class of missing-integrity-signal failure. In a community app, undetected gaps corrupt conversation context, cause replies to appear to reference messages that were never received, and erode user trust in the platform's reliability.

## Severity rationale

High because clients cannot detect dropped messages during network outages, silently corrupting conversation state and breaking reply threading.

## Remediation

Attach a per-channel monotonic sequence counter to every outgoing message at the server. Clients compare consecutive sequence numbers to detect gaps and trigger a catch-up request.

```ts
const channelSeq = new Map<string, number>();

socket.on('send_message', (data: { channel: string; content: string }) => {
  const seq = (channelSeq.get(data.channel) ?? 0) + 1;
  channelSeq.set(data.channel, seq);

  const message = {
    id: nanoid(),
    seq,
    channel: data.channel,
    content: data.content,
    userId: socket.userId,
    timestamp: Date.now(),
  };

  io.to(data.channel).emit('message', message);
});
```

For multi-instance deployments, store the counter in Redis (`INCR channel:{id}:seq`) rather than in process memory so the sequence is consistent across pods.

## Detection

- **ID:** `monotonic-sequence-numbers`
- **Severity:** `high`
- **What to look for:** Enumerate all message sending paths. For each, check for a sequence number or version counter attached to each message. Count the paths that include monotonic sequence numbers.
- **Pass criteria:** 100% of message sending paths include a sequence number that increments monotonically (1, 2, 3, ...) per channel. Clients can detect missed messages by comparing consecutive sequence numbers. Gaps of at least 1 indicate missed messages.
- **Fail criteria:** Messages lack sequence numbers, or sequence numbers are not monotonic.
- **Skip (N/A) when:** Never — sequence numbers are essential for detecting message loss.
- **Detail on fail:** `"Messages sent without sequence numbers. Clients cannot detect if messages were lost during network outages."`
- **Remediation:** Attach monotonic sequence numbers to messages at the server:

  ```ts
  interface Message {
    id: string;
    seq: number;  // Monotonic per channel
    content: string;
    userId: string;
    timestamp: number;
  }

  let channelSeq = new Map<string, number>();

  socket.on('send_message', (data: { channel: string; content: string }) => {
    const seq = (channelSeq.get(data.channel) ?? 0) + 1;
    channelSeq.set(data.channel, seq);

    const message: Message = {
      id: nanoid(),
      seq,
      content: data.content,
      userId: socket.userId,
      timestamp: Date.now(),
    };

    io.to(data.channel).emit('message', message);
  });
  ```

## External references

- cwe CWE-354
- iso-25010 security.integrity

Taxons: data-integrity

HTML version: https://auditbuffet.com/patterns/ab-000740
