Structured rules implement the v0.2 Diagnosis-as-Code contract for incidents that contain fields and evidence beyond one log string. Evaluation is local, deterministic, model-free, and independent of the Lumis hosted product.
Each file contains one strict DiagnosisRule:
apiVersion: lumis.dev/v1
kind: DiagnosisRule
metadata:
name: missing-required-column
version: "1"
spec:
priority: 100
match:
all:
- field: log.text
contains: KeyError
- field: schema.diff.removed_count
greaterThan: 0
- field: components.references
anyElement:
prefix: dbt.model.
any:
- field: component.type
equals: transformation
- field: labels.pipeline_domain
equals: data
not:
- field: incident.status
equals: resolved
diagnosis:
classification: schema_change
severity: high
summary: A required field was unavailable.
hypothesis: The upstream schema or normalization mapping changed.
confidence: 0.8
confirmedFacts:
- The current schema contains at least one removed field.
missingEvidence:
- Previous successful schema
- Upstream change record
evidence:
required: [schema_diff]
recommendedNextSteps:
- Compare the current and previous successful schemas.
suggestedPlaybook: investigate_schema_contractUnknown fields fail validation. Every condition defines exactly one operator:
contains: case-insensitive substring comparison;equals: exact string, number, or boolean comparison;prefix: case-sensitive string prefix;matchesRegex: Python regular expression search, validated when the rule loads;greaterThan,greaterThanOrEqual,lessThan,lessThanOrEqual: numeric comparison.
List-valued fields use an explicit anyElement or allElements quantifier around one existing
scalar operator. anyElement passes when at least one scalar element passes. allElements passes
when every scalar element passes. Empty lists fail both quantifiers. Lists are limited to 100
scalar elements; nested collections and oversized lists fail closed rather than being flattened
or coerced to strings.
Fields use dot paths. Callers may supply nested mappings or literal dotted keys. all requires
every condition, any requires at least one when present, and not requires every listed
condition to be false.
spec.evidence.required is a hard match precondition: the rule cannot win until every required
kind is supplied. spec.diagnosis.missingEvidence is different: it records follow-up context
that would strengthen, contradict, or confirm an already matched hypothesis. A value cannot
appear in both lists; ambiguous duplication fails validation.
from pathlib import Path
from lumis_sdk.adapters.deterministic import diagnose_structured
from lumis_sdk.config import load_diagnosis_rule
from lumis_sdk.domain import EvidenceItem
rule = load_diagnosis_rule(Path("rules/missing-required-column.yml"))
result = diagnose_structured(
fields={
"log": {"text": "ERROR KeyError: customer_id"},
"schema": {"diff": {"removed_count": 1}},
"component": {"type": "transformation"},
"labels": {"pipeline_domain": "data"},
"incident": {"status": "open"},
},
rules=[rule],
evidence=[
EvidenceItem(
id="schema-diff-1",
source="schema-registry",
kind="schema_diff",
detail="customer_id was removed",
confidence=1.0,
reference="schema://orders/current-vs-previous",
)
],
)
if result.winner:
print(result.winner.rule_id)
print(result.selection_reason)
print(result.winner.matched_conditions)
print(result.winner.evidence_references)
print(result.diagnosis.missing_evidence)
else:
print(result.candidates[0].failed_conditions)
print(result.candidates[0].missing_evidence)Every candidate includes rule ID/version, priority, specificity, matched and failed conditions,
missing required evidence, and evidence references. The selected diagnosis separately exposes
configured outstanding evidence through DiagnosisResult.missing_evidence. Matching candidates
are ranked by descending priority, then descending specificity, then stable input order.
Specificity weights all conditions twice, then counts any, not, and required-evidence
entries. Quantified condition explanations include bounded actual values and
matched_element_indexes.
The project spec.rules.files list may point either to legacy DiagnosisRuleSet files or to
single DiagnosisRule files. A project must migrate the complete collection together; mixing
both formats is rejected to avoid ambiguous cross-engine ordering.
lumis rules validate --config lumis.yml
lumis rules test --rule rules/schema-change.yml --input fixtures/schema-change.jsonFixture input contains a fields object and an optional evidence array of EvidenceItem
objects. The command emits JSON suitable for CI assertions and editor integrations. Input is
bounded to one MiB and no network or model call is made.
Existing DeterministicRule and diagnose_text_with_explanation behavior remains available.
Migrate one complete project rule collection at a time:
- Create one
DiagnosisRulefile per legacy rule. - Use
metadata.nameas the oldidandmetadata.versionas the oldversion. - Replace every
all_containsterm with anallcondition onlog.text. - Move diagnosis fields under
spec.diagnosis. - Replace adopter-side list flattening with explicit
anyElementorallElementsconditions. - Add required evidence and structured conditions where reliable signals exist.
- Test matching, non-matching, empty/oversized lists, missing evidence, and tie fixtures.
- Replace the project rule file list only after the complete collection passes.
Confidence remains human-authored diagnostic calibration. It does not grant execution authority.