KTP/
github ↗
schemas

Sensor Config

created 23 August 2026 · last modified 23 August 2026
{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://kinetic-trust-protocol.net/specs/schemas/v2/sensor-config.json",
  "title": "KTP Sensor Feed Configuration",
  "description": "Configuration schema for Context Signals sensor feeds",
  "type": "object",
  "required": ["dimension", "feeds"],
  "properties": {
    "dimension": {
      "type": "string",
      "enum": ["evidence_density", "trust_trend", "adversarial_pressure", "moment_criticality", "update_resistance", "attestation_coverage", "soul"],
      "description": "The Risk Factor this configuration applies to, or soul for the veto"
    },
    "feeds": {
      "type": "array",
      "items": {
        "$ref": "#/$defs/feed"
      },
      "minItems": 1,
      "description": "Array of sensor feeds for this Risk Factor"
    },
    "aggregation": {
      "type": "string",
      "enum": ["weighted_average", "any_veto", "all_veto", "max", "min"],
      "default": "weighted_average",
      "description": "How to aggregate multiple feeds (the soul veto must use any_veto)"
    },
    "default_on_failure": {
      "type": "number",
      "minimum": 0.5,
      "maximum": 1,
      "default": 1,
      "description": "Substitute value for feeds that are unavailable or stale. This stands in for an UNKNOWN observation, not for a measurement of zero risk, so it is floored at 0.5 - the value ktp-core 5.2 glosses as moderate stress. Zero is a measurement of perfect conditions and MUST NOT be used to represent an absent one; a deployment able to declare it could raise its own trust score by disabling sensors. Required unless aggregation is any_veto."
    }
  },
  "allOf": [
    {
      "if": {
        "properties": { "aggregation": { "not": { "const": "any_veto" } } }
      },
      "then": { "required": ["default_on_failure"] }
    },
    {
      "if": {
        "properties": { "aggregation": { "const": "any_veto" } },
        "required": ["aggregation"]
      },
      "then": {
        "properties": {
          "default_on_failure": {
            "const": 1,
            "description": "A veto-aggregated dimension has no partial state. If its feeds are unavailable, no clearance has been returned and the veto stands."
          }
        }
      }
    }
  ],
  "$defs": {
    "feed": {
      "type": "object",
      "required": ["id", "source", "enabled"],
      "properties": {
        "id": {
          "type": "string",
          "pattern": "^[a-z0-9-]+$",
          "description": "Unique identifier for this feed within the dimension"
        },
        "type": {
          "type": "string",
          "enum": ["physical", "network", "security", "application", "governance"],
          "description": "Category of sensor"
        },
        "source": {
          "type": "string",
          "description": "URI of the data source (mqtt://, https://, local://)"
        },
        "enabled": {
          "type": "boolean",
          "description": "Whether this feed is active"
        },
        "weight": {
          "type": "number",
          "minimum": 0,
          "default": 1.0,
          "description": "Weight of this feed in aggregation (for weighted dimensions)"
        },
        "normalization": {
          "$ref": "#/$defs/normalization"
        },
        "refresh_interval_ms": {
          "type": "integer",
          "anyOf": [
            { "const": 0 },
            { "minimum": 100 }
          ],
          "description": "How often to poll this source (milliseconds). 0 means the feed is not polled and is queried on demand at decision time, which is how the sovereignty feeds are configured."
        },
        "stale_threshold_ms": {
          "type": "integer",
          "anyOf": [
            { "const": 0 },
            { "minimum": 1000 }
          ],
          "description": "How long before an observation is no longer current. Past this age the feed is UNAVAILABLE and ktp-sensors 6.2 governs; the last known value MUST NOT be substituted. 0 means no cached observation is ever current - the pairing for an on-demand feed. Where this is omitted it defaults to five times refresh_interval_ms (ktp-sensors 6.1)."
        },
        "veto_on_match": {
          "type": "boolean",
          "default": false,
          "description": "For a soul feed: whether a match triggers the veto"
        },
        "transform": {
          "type": "string",
          "description": "Optional JSONPath or transformation expression"
        }
      }
    },
    "normalization": {
      "type": "object",
      "properties": {
        "min": {
          "type": "number",
          "description": "Raw value that maps to 0"
        },
        "max": {
          "type": "number",
          "description": "Raw value that maps to 1"
        },
        "invert": {
          "type": "boolean",
          "default": false,
          "description": "Whether to invert the scale (1 becomes 0, 0 becomes 1)"
        },
        "clamp": {
          "type": "boolean",
          "default": true,
          "description": "Whether to clamp values outside min/max to 0/1"
        }
      }
    }
  },
  "examples": [
    {
      "dimension": "evidence_density",
      "feeds": [
        {
          "id": "co2-sensor",
          "type": "physical",
          "source": "mqtt://sensors.local/building/co2",
          "enabled": true,
          "weight": 1.0,
          "normalization": {
            "min": 400,
            "max": 2000,
            "invert": false
          },
          "refresh_interval_ms": 30000,
          "stale_threshold_ms": 120000
        },
        {
          "id": "badge-count",
          "type": "physical",
          "source": "https://access.local/api/v1/count",
          "enabled": true,
          "weight": 1.5,
          "normalization": {
            "min": 0,
            "max": 5000,
            "invert": false
          },
          "refresh_interval_ms": 60000,
          "stale_threshold_ms": 300000
        }
      ],
      "aggregation": "weighted_average",
      "default_on_failure": 0.8
    },
    {
      "dimension": "soul",
      "feeds": [
        {
          "id": "tk-labels",
          "type": "governance",
          "source": "https://api.localcontexts.org/v1/labels",
          "enabled": true,
          "veto_on_match": true,
          "refresh_interval_ms": 0,
          "stale_threshold_ms": 0
        },
        {
          "id": "ocap-registry",
          "type": "governance",
          "source": "https://ocap.example.org/api/check",
          "enabled": true,
          "veto_on_match": true,
          "refresh_interval_ms": 0,
          "stale_threshold_ms": 0
        },
        {
          "id": "sacred-geofence",
          "type": "governance",
          "source": "local://geofence-service",
          "enabled": true,
          "veto_on_match": true,
          "refresh_interval_ms": 0,
          "stale_threshold_ms": 0
        }
      ],
      "aggregation": "any_veto"
    }
  ]
}