{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://golfsga.com/sga-ois/schema/0.1/going-away.schema.json",
  "title": "SGA-OIS going away",
  "description": "The engine closing a connection ON PURPOSE, and saying when to come back.\n\nWithout this a planned restart is indistinguishable from a crash. A socket simply closes, and every participant applies its normal backoff -- exponential, jittered, capped at five seconds -- which is exactly right for a crash and exactly wrong for a thirty-second update, where it produces a reconnect storm against an engine that is trying to start.\n\nSent to every affected participant before the socket closes.",
  "type": "object",
  "required": [
    "type",
    "reason"
  ],
  "additionalProperties": false,
  "patternProperties": {
    "^x-[a-z0-9_-]+\\.": {}
  },
  "properties": {
    "sga_kind": {
      "const": "going-away",
      "description": "Document type discriminator. Optional on the wire."
    },
    "type": {
      "const": "session.going_away",
      "description": "Message type on the live path."
    },
    "reason": {
      "$ref": "common.schema.json#/$defs/goingAwayReason"
    },
    "reconnect_after_ms": {
      "type": "integer",
      "minimum": 0,
      "description": "Wait this long, then reconnect. ABSENT means do not reconnect -- there is nothing to come back to until something changes at the bay, which is the normal case for 'superseded'.\n\nA participant MUST apply the same jitter it applies to backoff. Two hundred devices told to return in thirty seconds must not all return at thirty seconds exactly, or the interval has merely scheduled the storm it was meant to prevent."
    },
    "resume": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": {
        "^x-[a-z0-9_-]+\\.": {}
      },
      "description": "What the engine intends to preserve across the gap, so a participant knows whether it is rejoining a round or starting one. Absent means the session does not survive.",
      "properties": {
        "session_id": {
          "type": "string",
          "maxLength": 128,
          "description": "The session that will still be in force. Reconnection resumes by this plus last received seq, exactly as after an unplanned drop."
        },
        "session_epoch": {
          "type": "integer",
          "minimum": 0,
          "description": "The epoch the session will resume in. A restart is a clock rollover: t_ms begins again at 0 and the epoch increments, so ordering stays a (session_epoch, t_ms) comparison and nothing ever runs backwards."
        },
        "journal_head": {
          "$ref": "common.schema.json#/$defs/digest",
          "description": "Head of the hash chain at the moment of the pause. The journal is chained and signed; a restart MUST continue that chain rather than begin a new one, or the round's tamper-evidence breaks precisely at the seam -- which is the first place a reviewer looks."
        },
        "standing_state_retained": {
          "type": "boolean",
          "description": "Whether standing values -- which club is in hand, what the platform is set to -- survive. When false a participant re-asserts after reconnecting. Either way the engine republishes the manifest on resume, which is already how anything changing is announced."
        }
      }
    },
    "advisory": {
      "type": "string",
      "maxLength": 300,
      "description": "Human-readable, for an operator display. Never parsed."
    }
  }
}
