Schema validation

Sphinx-Needs (≥ 6.0) includes a powerful schema validation system based on JSON Schema. It lets you define declarative rules that validate individual need properties (local validation) and relationships between needs (network validation).

ubCode supports schema validation directly and can provide diagnostics in the editor as you type.

For the full sphinx-needs schema validation reference, see the sphinx-needs schema documentation.

Overview

Schema validation operates at multiple levels:

  1. Field-level schemas — defined inline on [needs.fields] or [needs.links], these validate every value entered for that field globally (see Field schemas).

  2. Schema definitions — defined in a schemas.json file (via schema_definitions_from_json), these provide advanced validation including type-specific rules, conditional constraints, and cross-need (network) validation.

Both levels complement each other: field-level schemas enforce global constraints for a field, while schema definitions allow complex, type-specific validation logic.

Configuration

schema_definitions_from_json

Type: string (default: "")

Path to a JSON file containing schema definitions. Available since sphinx-needs 6.0.

[needs]
schema_definitions_from_json = "schemas.json"

The schemas.json file

The schema file has two top-level keys:

  • "$defs" (optional): Reusable schema components referenced via $ref

  • "schemas" (required): Array of validation rule objects

{
  "$defs": {
    "type-spec": {
      "properties": {
        "type": { "const": "spec" }
      }
    },
    "safe-need": {
      "properties": {
        "asil": { "enum": ["A", "B", "C", "D"] }
      },
      "required": ["asil"]
    }
  },
  "schemas": [
    {
      "id": "spec-id-pattern",
      "severity": "warning",
      "message": "Spec IDs must be uppercase",
      "select": {
        "$ref": "#/$defs/type-spec"
      },
      "validate": {
        "local": {
          "properties": {
            "id": { "pattern": "^SPEC_[A-Z0-9_]+$" }
          }
        }
      }
    }
  ]
}

Each schema object in the schemas array supports:

  • id (optional): Identifier for the schema rule (used in error messages)

  • severity (optional): "violation" (default), "warning", or "info"

  • message (optional): Custom message shown when validation fails

  • select (optional): JSON Schema that filters which needs this rule applies to. If omitted, the rule applies to all needs.

  • validate (required): Validation rules with two subsections:

    • local: Validates properties of the need itself

    • network: Validates relationships with linked needs

Type system

Sphinx-Needs supports the following types for need fields, defined via schema.type in [needs.fields]:

  • Primitive types: string, boolean, integer, number

  • Array types: array with typed items (e.g. schema = {type = "array", items = {type = "string"}})

Type information from [needs.fields] is automatically injected into schemas.json rules, so you don’t need to repeat "type": "string" in every schema. If you do specify a type in the schema file, it must match the field definition.

Local validation

Local validation checks properties of a single need without any information from other needs. This makes it suitable for instant feedback in IDEs.

Example: enforce that efforts is between 0 and 20, and that approval is required when efforts exceed 15:

{
  "schemas": [
    {
      "id": "efforts-range",
      "select": {
        "properties": { "type": { "enum": ["spec", "feat"] } }
      },
      "validate": {
        "local": {
          "properties": {
            "efforts": {
              "minimum": 0,
              "maximum": 20
            }
          }
        }
      }
    },
    {
      "id": "approval-required-for-high-effort",
      "severity": "violation",
      "message": "Approval required when efforts > 15",
      "select": {
        "properties": {
          "type": { "enum": ["spec", "feat"] },
          "efforts": { "minimum": 16 }
        },
        "required": ["efforts"]
      },
      "validate": {
        "local": {
          "required": ["approval"]
        }
      }
    }
  ]
}

The select filter narrows which needs the rule applies to. The validate.local section uses standard JSON Schema properties: properties, required, allOf, anyOf, oneOf, not, unevaluatedProperties, etc.

Network validation

Network validation checks relationships between linked needs. After link resolution, the validator follows outgoing links and validates properties of the target needs.

Example: a safe implementation must link to at least one safe, approved specification:

{
  "id": "safe-impl-links",
  "message": "Safe impl must link to approved safe specs",
  "select": {
    "allOf": [
      { "$ref": "#/$defs/type-impl" },
      { "$ref": "#/$defs/safe-need" }
    ]
  },
  "validate": {
    "network": {
      "links": {
        "contains": {
          "local": {
            "allOf": [
              { "$ref": "#/$defs/type-spec" },
              { "$ref": "#/$defs/safe-need" },
              {
                "properties": {
                  "approval": { "const": true }
                }
              }
            ]
          }
        },
        "minContains": 1
      }
    }
  }
}

Key network validation properties:

  • validate.network.<link_type>.items — schema that all linked needs must match

  • validate.network.<link_type>.contains — schema that some linked needs must match

  • minContains / maxContains — how many linked needs must satisfy the contains schema

  • validate.local.properties.<link_type>.minItems / maxItems — total link count constraints (checked locally since links are ID lists)

Network validation can be nested to validate multi-hop chains (e.g. impl → spec → feat), up to 4 levels deep.

Reusable definitions ($defs)

Define common schema fragments in $defs and reference them with $ref:

{
  "$defs": {
    "type-impl": {
      "properties": { "type": { "const": "impl" } }
    },
    "type-spec": {
      "properties": { "type": { "const": "spec" } }
    },
    "safe-need": {
      "properties": {
        "asil": { "enum": ["A", "B", "C", "D"] }
      },
      "required": ["asil"]
    },
    "safe-spec": {
      "allOf": [
        { "$ref": "#/$defs/safe-need" },
        { "$ref": "#/$defs/type-spec" }
      ]
    }
  },
  "schemas": []
}

$ref must be the only key in the object where it appears. Recursive references are not allowed.

Severity levels

Each schema rule can specify a severity:

  • "info" — informational, logged as a Sphinx warning

  • "warning" — logged as a Sphinx warning

  • "violation" — logged as a Sphinx error (default)

{
  "severity": "warning",
  "message": "Consider adding a priority field",
  "validate": {
    "local": {
      "required": ["priority"]
    }
  }
}

Validation messages can be suppressed using Sphinx’s suppress_warnings:

# In conf.py
suppress_warnings = [
    "sn_schema_violation",       # all violations
    "sn_schema_warning",         # all warnings
    "sn_schema_info",            # all info messages
    "sn_schema_violation.local_fail",  # only local failures
]

Supported field constraints

The following JSON Schema constraints are available per type. These can be used both in [needs.fields] inline schemas and in schemas.json validation rules.

String: minLength, maxLength, pattern, format, enum, const

Integer / Number: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, enum, const

Boolean: const

Array: items (with sub-schema), minItems, maxItems, uniqueItems, contains, minContains, maxContains

String format values: "date", "date-time", "time", "duration", "email", "uri", "hostname", "uuid", "regex"

Note

Regex pattern values must be cross-engine compatible (Python, Rust, SQLite). Avoid lookaheads, lookbehinds, backreferences, and nested quantifiers.

Complete example

The following shows a ubproject.toml with typed fields and a schemas.json that validates them:

ubproject.toml:

[needs]
id_required = true
schema_definitions_from_json = "schemas.json"

[[needs.types]]
directive = "feat"
title = "Feature"
prefix = "FEAT_"

[[needs.types]]
directive = "spec"
title = "Specification"
prefix = "SPEC_"

[[needs.types]]
directive = "impl"
title = "Implementation"
prefix = "IMPL_"

[needs.links.implements]
outgoing = "implements"
incoming = "implemented by"

[needs.fields.efforts]
schema = {type = "integer", minimum = 0, maximum = 100}

[needs.fields.approval]
schema = {type = "boolean"}

[needs.fields.asil]
schema = {type = "string", enum = ["QM", "A", "B", "C", "D"]}

schemas.json:

{
  "$defs": {
    "type-impl": {
      "properties": { "type": { "const": "impl" } }
    },
    "type-spec": {
      "properties": { "type": { "const": "spec" } }
    },
    "safe-need": {
      "properties": {
        "asil": { "enum": ["A", "B", "C", "D"] }
      },
      "required": ["asil"]
    }
  },
  "schemas": [
    {
      "id": "effort-limits",
      "message": "Efforts must be between 0 and 20 for specs",
      "select": {
        "properties": { "type": { "const": "spec" } }
      },
      "validate": {
        "local": {
          "properties": {
            "efforts": { "maximum": 20 }
          }
        }
      }
    },
    {
      "id": "safe-impl-chain",
      "message": "Safe impl must link to safe spec",
      "select": {
        "allOf": [
          { "$ref": "#/$defs/type-impl" },
          { "$ref": "#/$defs/safe-need" }
        ]
      },
      "validate": {
        "network": {
          "implements": {
            "contains": {
              "local": {
                "allOf": [
                  { "$ref": "#/$defs/type-spec" },
                  { "$ref": "#/$defs/safe-need" }
                ]
              }
            },
            "minContains": 1
          }
        }
      }
    }
  ]
}

Further reading

For the complete reference including all validation options, error message formats, debug tooling, and migration guides, see the sphinx-needs schema validation documentation.