opentide

Models

Pydantic models for rules, objectives, and threats — key fields, delegation methods, results, and errors.

Core objects are Pydantic v2 models with explicit schema identifiers. This page is the Python-side field reference; the full normative field contract lives in the specifications.

Core objects

ModelSchema IDModuleSpec
DetectionRulerule::1.0opentide.models.rulerule-1.0
DetectionRule_v1_1rule::1.1opentide.models.rulerule-1.1
DetectionObjectiveobjective::1.0opentide.models.objectiveobjective-1.0
ThreatVectorthreat::1.0opentide.models.threatthreat-1.0
from opentide.models.rule import DetectionRule
DetectionRule.__schema_identifier__  # "rule::1.0"

DetectionRule — key fields

AttributeTypeNotes
namestrDisplay name
metadataObjectMetadataIdentity (uuid), schema_id, version, tlp
descriptionstrRule narrative
statusstrDeployment status (default STAGING)
severitystrSeverity vocabulary (default Informational)
techniqueslist[str]ATT&CK technique IDs
configurationsRuleConfigurationsTyped per-platform blocks (preferred)
platformsdictLegacy flat platform dict
detection_modelstr | NoneUUID of the objective, or of a signal under it, that this rule implements
responseRuleResponse | NoneAlert + response playbook

The YAML you author maps one-to-one onto these attributes:

rule = Opentide.Rules["00000000-0000-4000-8003-000000000001"]
rule.name                       # "Sentinel KQL Rule"
rule.metadata.schema_id         # "rule::1.0"  (YAML `metadata.schema`)
rule.metadata.schema            # "rule::1.0"  (read-only alias of schema_id)
rule.detection_model            # objective UUID (or a signal UUID under one)
rule.configurations.sentinel.query

ObjectMetadata.schema_id is the field. Validation accepts it under the YAML key schema or as schema_id, and model_dump(by_alias=True)["schema"] writes it back under schema. Platform blocks follow the same pattern: platform_schema is the field, and rule.configurations.sentinel.schema reads it (None when the block declares no schema). In 0.5.0 and earlier, .schema on either model returned Pydantic's deprecated BaseModel.schema method, not the identifier; read schema_id if your code must support those releases.

Platform configuration models

rule.configurations is a RuleConfigurations (opentide.models.platform). Each block subclasses PlatformConfigBase (enabled, name, platform_schema, status, flags, tenants, contributors). Nested objects live in opentide.models.platform_configs. The normative field contract is the platform spec, not this table.

ModelYAML keySchema IDSpec
SentinelConfigsentinelplatform::sentinel::1.0sentinel-1.0
DefenderConfigdefender_for_endpointplatform::defender_for_endpoint::1.0defender-for-endpoint-1.0
SplunkConfigsplunkplatform::splunk::1.0splunk-1.0
SentinelOneConfigsentinel_oneplatform::sentinel_one::1.0sentinel-one-1.0
CrowdstrikeConfigcrowdstrikeplatform::crowdstrike::1.0crowdstrike-1.0
HarfangLabConfigharfanglabplatform::harfanglab::1.0harfanglab-1.0
CarbonBlackConfigcarbon_black_cloudplatform::carbon_black_cloud::1.0carbon-black-cloud-1.0
rule.configurations.sentinel.query
rule.configurations.sentinel.schema          # YAML `schema`, or None
rule.configurations.sentinel.scheduling.frequency  # "PT1H"

These classes implement the platform specs. The specs list every key. This page only names the class that holds each block.

DetectionObjective — key fields

AttributeTypeNotes
namestrDisplay name
metadataObjectMetadataShared metadata
compositionObjectiveCompositionTop-level strategy (mirrors objective.composition)
objectiveObjectiveBodyBody: priority, type, signals, threats, …
obj = Opentide.Objectives["00000000-0000-4000-8002-000000000001"]
obj.objective.threats           # ["…8001…"]  (threat UUIDs)
[s.name for s in obj.objective.signals]

ThreatVector — key fields

AttributeTypeNotes
namestrDisplay name
criticalitystrCriticality vocabulary
metadataObjectMetadataShared metadata
threatThreatBodyBody: severity, impact and leverage (non-empty list[str] of vocabulary names), terrain, surface, att&ck, actors (list[ThreatActor]), optional cve (list[str], Vulnerability-Lookup IDs), …
threat = Opentide.Threats["00000000-0000-4000-8001-000000000001"]
threat.threat.mitre_attack      # ["T1059"]  (YAML `mitre_attack`)
threat.threat.terrain           # free-form prose
threat.threat.surface           # vocabulary list, e.g. ["OS::Windows::Desktop"]
threat.threat.impact            # vocabulary list, e.g. ["Data Breach", "Identity Theft"]
threat.threat.cve               # list[str] | None, e.g. ["CVE-2024-3094"] — CIRCL Vulnerability-Lookup (CVE, GHSA, GCVE)
threat.threat.actors            # list[ThreatActor] | None; name is scoped (att&ck::G0006)

Delegation methods

Rules loaded through the registry are bound to it and expose operations directly:

rule = Opentide.Rules[uuid]

rule.validate() -> ValidationResult                      # schema + cross-object checks
rule.validate_query(platform) -> ValidationResult        # query syntax (supported platforms)
rule.run_query(platform, *, tenants=(), since=None, limit=100) -> QueryRun  # rows from the tenants
rule.deploy(platform, dry_run=False, *, strategies=None) -> DeploymentResult
rule.document() -> str                                   # or Opentide.render_rule(rule)

validate_query runs the platform's query validator when one exists (Sentinel, Defender, Splunk, SentinelOne, Carbon Black); for CrowdStrike/HarfangLab it reports unsupported rather than faking a pass. See Validation → query validation.

run_query sends the query this rule deploys to platform to the tenants tenants names (["all"] for every one; none for the only one) over the last since (default 24h), and returns an opentide.query.QueryRun: outcomes holds one TenantOutcome per tenant, with result.columns and result.rows or an error. A call that cannot run raises opentide.query.QueryUsageError. It is opentide query --rule in Python; opentide.query.run_query(platform, query=...) runs a query that is not a rule's.

Opentide.promote_rule() raises NotImplementedError — change rule status in YAML and use opentide deploy (promotion runs inside deploy when configured).

Results

Delegation methods return small Pydantic result models (opentide.models.results):

class ValidationResult(BaseModel):
    ok: bool
    errors: list[str] = []
    warnings: list[str] = []

class DeploymentResult(BaseModel):
    platform: str
    uuids: list[str]
    dry_run: bool = False
    message: str = ""
result = rule.validate()
if not result.ok:
    for err in result.errors:
        print(err)

Errors

Exceptions live under opentide.core.errors.Errors (aliased TideErrors). Catch the specific type you expect:

ExceptionRaised when
Errors.TideDataModelErrorsMalformed Tide object (bad structure)
Errors.TideRuleDataModelErrorsInvalid detection rule data structure. Errors.TideMDRDataModelErrors is the former name, kept as an alias
Errors.TideQueryValidationErrorA rule query fails validation
Errors.TideDeploymentErrorsAny failure during deployment
Errors.TenantConnectionErrorCannot connect to the target tenant. Live query validation raises the subclass TenantUnreachableError (opentide.platforms.transport), which names the tenant and host
Errors.DetectionRuleCreationFailed / …UpdateFailed / …DeletionFailedPlatform API operation failed
Errors.TideConfigurationErrorsInvalid opentide configuration files
Errors.TenantNonExistingDeploymentPlanA tenant references a missing deployment plan
from opentide.core.errors import Errors

try:
    rule.deploy("sentinel")
except Errors.TenantConnectionError:
    ...  # credentials / connectivity
except Errors.TideDeploymentErrors:
    ...  # deployment failure

Note the difference between results (returned for expected pass/fail outcomes like validation) and errors (raised for exceptional failures like a broken connection).

Loading without the registry

from opentide.loading.rule_loader import load_rule_from_dict
from opentide.loading.object_loader import load_object_for_validation

Prefer registry access for interactive work; these loaders are used internally by validation and indexing.

Both pick the model from metadata.schema. A missing identifier raises SchemaMissingError (a ValueError from opentide.loading.object_loader). An unknown identifier, or one of another object type, raises SchemaIdentifierError (a LookupError from the same module). Neither falls back to the newest revision.

Source

src/opentide/models/, src/opentide/models/results.py, src/opentide/core/errors.py

On this page