Skip to content

Rule Decisions and Events

L2Proxy emits structured JSON lines that can be retained locally, forwarded to a log pipeline, or stored in PostgreSQL/jsonb. These examples show the operational distinction between a decision, a state mutation, and a timeout.

Traceability from policy decision to runtime evidence records

Figure — Rule decisions remain evidence-linked for review and audit.

These Rule Engine records explain which policy matched and what decision was made. The complementary Industrial Event Normalizer explains which industrial operation affected which customer asset. Both can be correlated by time, frame, endpoint, protocol identity, and selected rule metadata.

Logged rule decision

{
  "source": "rule_engine",
  "event": "rule_match",
  "rule": "dnp3-sbo-drop-invalid-operate",
  "order": 60,
  "frame_number": 1849,
  "verdict": "drop",
  "stop": true,
  "src_mac": "00:11:22:33:44:55",
  "dst_mac": "00:aa:bb:cc:dd:ee",
  "src_ip": "10.20.1.10",
  "dst_ip": "10.20.2.100",
  "src_port": 49152,
  "dst_port": 20000,
  "layers": ["Ethernet", "IPv4", "TCP", "DNP3"],
  "meta": {
    "policy": "sbo_required",
    "violation": "operate_without_matching_select",
    "master": 3,
    "outstation": 100,
    "targets": 1,
    "fingerprint": "sha256:7e..."
  }
}

Fixed fields support consistent queries. meta carries customer-selected industrial evidence and is evaluated only for a logged match.

Atomic state action

{
  "source": "rule_engine",
  "event": "state_action",
  "rule": "dnp3-sbo-consume-operate",
  "action": "transition_state",
  "key": "dnp3:sbo:3:100",
  "from": "selected|sha256:7e...",
  "value": "consumed",
  "ttl": 1,
  "applied": true,
  "frame_number": 1848
}

applied: true proves that this packet won the compare-and-transition. A concurrent or replayed packet receives applied: false and follows the violation branch.

Packet-independent timeout

{
  "source": "rule_engine",
  "event": "state_timeout",
  "rule": "command-await-feedback",
  "key": "dnp3:command-status:100:crob7",
  "value": "awaiting_status|on",
  "ttl": 15
}

Timeout events have no frame number because no packet is required. They are available when the optional scheduler is enabled. The scheduler reports expiration; it does not evaluate another packet rule by itself.

Runtime expression error

{
  "source": "rule_engine",
  "event": "rule_evaluation_error",
  "rule": "customer-critical-control",
  "frame_number": 1850,
  "error": "state expression returned nil"
}

The configured runtime error policy determines whether evaluation continues, accepts, or drops. This decision must be part of the customer's enforcement design.

Example PostgreSQL/jsonb query

SELECT
  body->>'rule' AS rule_name,
  body->>'verdict' AS verdict,
  body->'meta'->>'outstation' AS outstation,
  count(*) AS events
FROM logs
WHERE body->>'source' = 'rule_engine'
  AND body->>'event' = 'rule_match'
GROUP BY 1, 2, 3
ORDER BY events DESC;

Table and column names depend on the customer's integration. The JSON keys shown above match the Rule Engine event contract.