Watch rules
Declare behavioural-pattern detections in your ontology extension — score role build-ups against signature weights, suppress re-fires, and optionally trigger a response process flow when a detection fires.
Watch rules are authored inside an Ontology extension alongside your Object Types, Link Types, and Bindings. For the extension lifecycle — extension build, upload, install — see Extensions & Authoring SDK. For the detection runtime and how fired detections propagate, see the Ontology documentation.
A watch rule continuously monitors a time-windowed stream of ontology observations and fires a detection when the observed role composition crosses a threshold. You declare what to watch, how to score it, and what to do when it fires — all in code, versioned alongside the rest of your extension.
Declaring a watch rule
Add a watchRules array to your defineOntology call:
import { defineOntology } from '@scrydon/sdk-authoring/ontologies'
export default defineOntology({
id: 'c2-threat-awareness',
version: '1.0.0',
displayName: 'C2 Threat Awareness',
// ... object types, link types, bindings ...
watchRules: [
{
slug: 'kaliningrad-air-buildup',
displayName: 'Kaliningrad — allied air build-up',
description: 'Detects a concentration of ELECTRONIC_WARFARE, AEW, and FIGHTER airframes near the Kaliningrad approaches.',
target: {
objectTypeSlug: 'Aircraft',
entityProperty: 'icao24', // collapse positional fixes → airframes
timeProperty: 'seenAt',
latProperty: 'latitude',
lngProperty: 'longitude',
roleVia: { linkTypeSlug: 'AircraftOfType', property: 'missionRole' },
},
area: { objectTypeSlug: 'AreaOfInterest', areaId: 'kaliningrad-approaches' },
windowSeconds: 900, // 15-minute rolling window
signature: {
weights: { ELECTRONIC_WARFARE: 5, AEW: 4, FIGHTER: 1 },
roleSaturation: 2, // cap each role's contribution at 2 entities
},
threshold: {
minDistinctRoles: 2,
minScore: 10,
},
suppression: {
cooldownSeconds: 3600, // suppress re-fires for 1 hour
refireOnNewRole: true, // unless a new role appears
refireOnScoreDelta: 4, // or the score changes by more than 4
},
emit: {
objectTypeSlug: 'BuildUpDetection',
clearanceCeiling: { schemeId: 'default', rank: 2 },
},
},
],
})Field reference
Top-level fields
| Field | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | Stable identifier for this watch rule. Must be a valid slug (lowercase letters, digits, and hyphens; no spaces). Used as the name of the emitted SCHEMA_WATCH declaration. |
displayName | string (1–100 chars) | Yes | Human-readable name shown in the platform UI. |
description | string (≤500 chars) | No | Optional longer description of what this watch detects. |
enabled | boolean | No | Whether the watch is active. Defaults to true when omitted. Set to false to ship a disabled watch that can be turned on without an extension re-publish. |
target | object | Yes | Defines which Object Type to observe and how to resolve entity identity and roles. See target below. |
area | object | Yes | Identifies the AreaOfInterest instance that defines the spatial boundary. |
windowSeconds | integer (1–3600) | Yes | Rolling time window in seconds (max 3600 — 1 hour). |
signature | object | Yes | Role weights and saturation cap. See signature below. |
threshold | object | Yes | Conditions that must both be met for a detection to fire. See threshold below. |
suppression | object | Yes | Re-fire control. See suppression below. |
emit | object | Yes | Output object type and clearance ceiling for the detection. See emit below. |
dispatch | object | No | Optional process-flow to start on detection. See dispatch below. |
target
| Field | Type | Description |
|---|---|---|
objectTypeSlug | string | The Object Type whose observations are scored. |
entityProperty | string | Property that identifies the persistent entity (e.g. icao24). Observations sharing this value are collapsed into one entity count. |
timeProperty | string | Timestamp property used to apply the rolling window. |
latProperty | string | Latitude property (WGS-84 decimal degrees). |
lngProperty | string | Longitude property (WGS-84 decimal degrees). |
roleVia | { linkTypeSlug, property } | Traverse this link type and read this property to determine the entity's role for scoring purposes. |
roleOverrideObjectTypeSlug | string? | When set, restrict role-resolution to entities of this Object Type on the far end of the link. |
area
An AreaOfInterest instance loaded from the ontology graph that defines the spatial boundary for the watch. Observations outside the area are excluded from scoring.
windowSeconds
Rolling time window in seconds. Maximum is 3600 (1 hour). Observations older than windowSeconds age out of the score.
signature
| Field | Type | Description |
|---|---|---|
weights | Record<string, number> | Score contribution per role. Keys must be role values present in the linked Object Type. Values must be ≥ 0 and finite. |
roleSaturation | number | Maximum entity count per role that contributes to the score. Prevents a single-role flood from masking a multi-role composition. |
threshold
| Field | Type | Description |
|---|---|---|
minDistinctRoles | number | Minimum number of distinct roles that must be present before the detection fires. |
minScore | number | Minimum weighted score that must be reached. |
Both conditions must be satisfied simultaneously for the detection to fire.
suppression
| Field | Type | Description |
|---|---|---|
cooldownSeconds | number | After a detection fires, suppress re-fires for this many seconds (max 86 400 — 24 h). |
refireOnNewRole | boolean | When true (default), a new role entering the window bypasses the cooldown and fires a new detection. |
refireOnScoreDelta | number | When the score changes by at least this amount, a new detection fires even within the cooldown window. Must be positive. |
emit
| Field | Type | Description |
|---|---|---|
objectTypeSlug | string | The Object Type to materialise for each fired detection. |
clearanceCeiling | { schemeId, rank } | Required. Caps the clearance of the emitted detection object. The evaluator also reads at this ceiling: observations marked above it are never read and cannot contribute, and each detection's marking is the join of the evidence it cites — so setting this explicitly is a hard requirement (fail-closed). |
dispatch (optional)
dispatch: {
extensionId: 'c2-drone-response',
processFlowSlug: 'buildup-response',
}When present, a fired detection starts the named process flow in addition to emitting the detection object. The flow receives a detectionId pointer only — no detection content is passed directly.
Clearance note. The dispatched flow resolves the detection under its own principals' clearance through the normal governed read path. It cannot access more of the detection than the principals executing it are cleared to see. Setting dispatch does not widen disclosure beyond the notification that already goes to the same audience.
| Field | Type | Constraints |
|---|---|---|
extensionId | string | 1–256 characters. Must identify the extension that publishes the target process flow. |
processFlowSlug | string | 1–256 characters. Slug of the process flow to start on detection. |
Both fields are exact identifiers — no wildcards or patterns.
Complete example with dispatch
watchRules: [
{
slug: 'kaliningrad-air-buildup',
displayName: 'Kaliningrad — allied air build-up',
target: {
objectTypeSlug: 'Aircraft',
entityProperty: 'icao24',
timeProperty: 'seenAt',
latProperty: 'latitude',
lngProperty: 'longitude',
roleVia: { linkTypeSlug: 'AircraftOfType', property: 'missionRole' },
},
area: { objectTypeSlug: 'AreaOfInterest', areaId: 'kaliningrad-approaches' },
windowSeconds: 900,
signature: {
weights: { ELECTRONIC_WARFARE: 5, AEW: 4, FIGHTER: 1 },
roleSaturation: 2,
},
threshold: { minDistinctRoles: 2, minScore: 10 },
suppression: {
cooldownSeconds: 3600,
refireOnNewRole: true,
refireOnScoreDelta: 4,
},
emit: {
objectTypeSlug: 'BuildUpDetection',
clearanceCeiling: { schemeId: 'default', rank: 2 },
},
// When the detection fires, start the response flow in the c2-drone-response extension.
// The flow receives a detectionId pointer and resolves content under its own clearance.
dispatch: {
extensionId: 'c2-drone-response',
processFlowSlug: 'buildup-response',
},
},
],Stability guarantee
watchRules is an optional field. Extensions that do not declare any watch rules keep compiling and hashing unchanged — adding this field to your extension version does not invalidate previously published manifests.
Within a watch rule, dispatch is also optional. Extensions that declared a watch rule before dispatch existed continue to parse and compile without modification.