{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "urn:orbiplex:schema:whisper-signal:v1",
  "title": "WhisperSignal v1",
  "description": "Machine-readable schema for the content body of an Agora record (or INAC artefact) expressing a bounded rumor-style social signal. The enclosing `agora-record.v1` envelope carries identity, authorship (including pseudonymous `nym:did:key:…` authorship and the attached nym-certificate reference), authorship signature, timestamping, and topic routing; this schema validates only the content body. Sender transport identifiers (node id, federation id) are peer-session concerns and are not part of the signed content body.",
  "type": "object",
  "additionalProperties": true,
  "x-dia-workflow": "project",
  "x-dia-status": "draft",
  "x-dia-basis": [
    "doc/project/20-memos/orbiplex-whisper.md",
    "doc/project/20-memos/orbiplex-anon.md",
    "doc/project/30-stories/story-005-whisper-rumor-intake.md",
    "doc/project/40-proposals/013-whisper-social-signal-exchange.md",
    "doc/project/40-proposals/015-nym-certificates-and-renewal-baseline.md",
    "doc/project/40-proposals/035-agora-topic-addressed-record-relay.md",
    "doc/project/40-proposals/041-agora-ingest-attestation.md",
    "doc/project/40-proposals/042-inter-node-artifact-channel.md"
  ],
  "required": [
    "schema",
    "signal/polarity",
    "epistemic/class",
    "signal/text",
    "topic/class",
    "context/facets",
    "confidence",
    "disclosure/scope",
    "signal/grade",
    "routing/profile",
    "routing/failure-mode",
    "forwarding/max-hops"
  ],
  "properties": {
    "schema": {
      "const": "whisper-signal.v1",
      "description": "Content-level discriminator for consumers that inspect the payload outside its Agora or INAC envelope."
    },
    "signal/polarity": {
      "type": "string",
      "enum": [
        "problem",
        "inspiration"
      ],
      "description": "Fundamental polarity of the whisper signal. `problem` describes a distributed harm, failure, or dignity risk that may require correlation and protective response. `inspiration` describes a convergent idea, creative discovery, or emerging approach that may justify co-creation. Emergency handling MUST NOT be triggered solely by an `inspiration` signal."
    },
    "epistemic/class": {
      "type": "string",
      "enum": [
        "rumor",
        "weak-signal"
      ],
      "description": "Explicit epistemic class that prevents the artifact from being treated as evidence."
    },
    "signal/text": {
      "type": "string",
      "minLength": 1,
      "description": "Sanitized text accepted by the local user before publication."
    },
    "signal/text-native": {
      "type": "string",
      "minLength": 1,
      "description": "Optional sanitized text in the original non-English language. Use this only when the original accepted user-facing wording is not English; English text stays solely in `signal/text`."
    },
    "signal/text-native-lang": {
      "type": "string",
      "pattern": "^(?!en(?:-|$))[A-Za-z]{2,3}(?:-[A-Za-z0-9]{2,8})*$",
      "description": "BCP 47-like language tag for `signal/text-native`. This field is intentionally non-English; English originals MUST omit both native-text fields and use only `signal/text`."
    },
    "topic/class": {
      "type": "string",
      "minLength": 1,
      "description": "Normalized issue class used for bounded correlation. Distinct from the enclosing envelope's `topic/key`, which is an Agora routing key; `topic/class` is the semantic correlation class carried inside the rumor body."
    },
    "signal/similarity-key": {
      "type": "string",
      "minLength": 1,
      "description": "Optional deterministic correlation key used by fixture-grade or policy-defined threshold engines. M4 uses `topic/class` plus this key for the first laptop smoke; semantic similarity remains a later policy or module concern."
    },
    "contact/ref": {
      "type": "string",
      "pattern": "^routing:did:key:z[1-9A-HJ-NP-Za-km-z]+$",
      "description": "Optional reply/contact routing subject. Consumers MUST NOT infer a participant or node route from nym authorship when this field is absent."
    },
    "context/facets": {
      "type": "array",
      "minItems": 1,
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Normalized, low-resolution facets that help correlation without forcing raw disclosure."
    },
    "confidence": {
      "type": "number",
      "minimum": 0,
      "maximum": 1,
      "description": "Local confidence in the quality and relevance of the prepared signal."
    },
    "disclosure/scope": {
      "type": "string",
      "enum": [
        "private-correlation",
        "federation-scoped",
        "cross-federation",
        "public-aggregate-only"
      ],
      "description": "Maximum disclosure posture allowed for this signal. Distribution-surface selection honours this: `private-correlation` SHOULD travel via INAC direct exchange (proposal 042); wider scopes MAY use Agora. The `SHOULD` is intentional — public Agora deployments SHOULD refuse `private-correlation` at ingest (its publication properties conflict with the disclosure intent), while closed / intra-organization Agora federations MAY carry these whispers internally under their own ingest policy."
    },
    "source/class": {
      "type": "string",
      "enum": [
        "direct-user",
        "pod-user",
        "operator-observed",
        "derived-local",
        "monus-derived",
        "monus-sensorium-derived"
      ],
      "description": "High-level origin class of the signal. `monus-derived` is used when a local Monus-like wellbeing module prepared the draft before Whisper publication. `monus-sensorium-derived` is used when Monus relied materially on Sensorium-originated local signals."
    },
    "source/signal-kinds": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Optional high-level local signal classes that materially informed a derived or sensorium-assisted rumor draft."
    },
    "signal/grade": {
      "type": "string",
      "enum": [
        "low",
        "moderate",
        "high",
        "critical"
      ],
      "description": "Signal salience grade. For `problem` signals this grades protective risk and urgency. For `inspiration` signals this grades convergence strength, co-creation potential, or urgency of matching interested participants. Routing and disclosure policy MAY interpret the same scale differently by `signal/polarity`."
    },
    "routing/profile": {
      "type": "string",
      "enum": [
        "direct",
        "relayed",
        "onion-relayed"
      ],
      "description": "Requested outbound transport posture."
    },
    "routing/failure-mode": {
      "type": "string",
      "enum": [
        "soft-fail",
        "hard-fail"
      ],
      "description": "Whether the sender allows downgrade if the requested transport posture cannot be satisfied."
    },
    "relay/acceptable-classes": {
      "type": "array",
      "items": {
        "type": "string",
        "minLength": 1
      },
      "description": "Optional relay classes acceptable for outbound privacy realization."
    },
    "forwarding/max-hops": {
      "type": "integer",
      "minimum": 0,
      "maximum": 3,
      "description": "Maximum number of relay hops allowed for the signal."
    },
    "forwarding/budget": {
      "type": "integer",
      "minimum": 0,
      "description": "Maximum number of bounded forwards allowed under local policy."
    },
    "policy_annotations": {
      "type": "object",
      "additionalProperties": true,
      "description": "Optional implementation- or federation-local annotations that do not change the core semantics."
    }
  },
  "allOf": [
    {
      "if": {
        "properties": {
          "routing/profile": {
            "const": "direct"
          }
        },
        "required": [
          "routing/profile"
        ]
      },
      "then": {
        "properties": {
          "forwarding/max-hops": {
            "const": 0
          }
        }
      }
    },
    {
      "if": {
        "properties": {
          "routing/failure-mode": {
            "const": "hard-fail"
          }
        },
        "required": [
          "routing/failure-mode"
        ]
      },
      "then": {
        "properties": {
          "routing/profile": {
            "enum": [
              "relayed",
              "onion-relayed"
            ]
          }
        }
      }
    }
  ],
  "dependentRequired": {
    "signal/text-native": [
      "signal/text-native-lang"
    ],
    "signal/text-native-lang": [
      "signal/text-native"
    ]
  }
}
