docs(alerting): align @JsonTypeInfo spec with shipped code
Design spec and Plan 02 described AlertCondition polymorphism as Id.DEDUCTION, but the code that shipped in PR #140 uses Id.NAME with property="kind" and include=EXISTING_PROPERTY. The `kind` field is real on every subtype and the DB stores it in a separate column (condition_kind), so reading the discriminator directly is simpler than deduction — update the docs to match. Also add `"kind"` to the example JSON payloads so they match on-wire reality. OutboundAuth (Plan 01) correctly still uses Id.DEDUCTION and is unchanged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -286,7 +286,7 @@ CREATE TABLE alert_rules (
|
||||
enabled boolean NOT NULL DEFAULT true,
|
||||
|
||||
condition_kind condition_kind_enum NOT NULL,
|
||||
condition jsonb NOT NULL, -- sealed-subtype payload, Jackson-DEDUCTION polymorphic
|
||||
condition jsonb NOT NULL, -- sealed-subtype payload, Jackson polymorphic on `kind`
|
||||
|
||||
evaluation_interval_seconds int NOT NULL DEFAULT 60 CHECK (evaluation_interval_seconds >= 5),
|
||||
for_duration_seconds int NOT NULL DEFAULT 0 CHECK (for_duration_seconds >= 0),
|
||||
@@ -423,14 +423,15 @@ outbound_connections (delete) — blocked by FK from rules.webhooks JSONB
|
||||
### Jackson polymorphism for conditions
|
||||
|
||||
```java
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION)
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "kind",
|
||||
include = JsonTypeInfo.As.EXISTING_PROPERTY, visible = true)
|
||||
@JsonSubTypes({
|
||||
@Type(RouteMetricCondition.class),
|
||||
@Type(ExchangeMatchCondition.class),
|
||||
@Type(AgentStateCondition.class),
|
||||
@Type(DeploymentStateCondition.class),
|
||||
@Type(LogPatternCondition.class),
|
||||
@Type(JvmMetricCondition.class),
|
||||
@Type(value = RouteMetricCondition.class, name = "ROUTE_METRIC"),
|
||||
@Type(value = ExchangeMatchCondition.class, name = "EXCHANGE_MATCH"),
|
||||
@Type(value = AgentStateCondition.class, name = "AGENT_STATE"),
|
||||
@Type(value = DeploymentStateCondition.class, name = "DEPLOYMENT_STATE"),
|
||||
@Type(value = LogPatternCondition.class, name = "LOG_PATTERN"),
|
||||
@Type(value = JvmMetricCondition.class, name = "JVM_METRIC"),
|
||||
})
|
||||
public sealed interface AlertCondition permits
|
||||
RouteMetricCondition, ExchangeMatchCondition, AgentStateCondition,
|
||||
@@ -439,37 +440,40 @@ public sealed interface AlertCondition permits
|
||||
}
|
||||
```
|
||||
|
||||
Jackson deduces the subtype from the set of present fields. Bean Validation (`@Valid`) on each record validates at the controller boundary.
|
||||
Each payload carries its own `kind` field, which Jackson reads (`EXISTING_PROPERTY`) to pick the subtype and the record still exposes as `ConditionKind kind()`. Bean Validation (`@Valid`) on each record validates at the controller boundary.
|
||||
|
||||
Example condition payloads:
|
||||
|
||||
```json
|
||||
// ROUTE_METRIC
|
||||
{ "scope": {"appSlug":"orders","routeId":"route-1"},
|
||||
{ "kind": "ROUTE_METRIC",
|
||||
"scope": {"appSlug":"orders","routeId":"route-1"},
|
||||
"metric": "P99_LATENCY_MS", "comparator": "GT", "threshold": 2000, "windowSeconds": 300 }
|
||||
|
||||
// EXCHANGE_MATCH — PER_EXCHANGE
|
||||
{ "scope": {"appSlug":"orders"},
|
||||
{ "kind": "EXCHANGE_MATCH",
|
||||
"scope": {"appSlug":"orders"},
|
||||
"filter": {"status":"FAILED","attributes":{"type":"payment"}},
|
||||
"fireMode": "PER_EXCHANGE", "perExchangeLingerSeconds": 300 }
|
||||
|
||||
// EXCHANGE_MATCH — COUNT_IN_WINDOW
|
||||
{ "scope": {"appSlug":"orders"},
|
||||
{ "kind": "EXCHANGE_MATCH",
|
||||
"scope": {"appSlug":"orders"},
|
||||
"filter": {"status":"FAILED"},
|
||||
"fireMode": "COUNT_IN_WINDOW", "threshold": 5, "windowSeconds": 900 }
|
||||
|
||||
// AGENT_STATE
|
||||
{ "scope": {"appSlug":"orders"}, "state": "DEAD", "forSeconds": 60 }
|
||||
{ "kind": "AGENT_STATE", "scope": {"appSlug":"orders"}, "state": "DEAD", "forSeconds": 60 }
|
||||
|
||||
// DEPLOYMENT_STATE
|
||||
{ "scope": {"appSlug":"orders"}, "states": ["FAILED","DEGRADED"] }
|
||||
{ "kind": "DEPLOYMENT_STATE", "scope": {"appSlug":"orders"}, "states": ["FAILED","DEGRADED"] }
|
||||
|
||||
// LOG_PATTERN
|
||||
{ "scope": {"appSlug":"orders"}, "level": "ERROR",
|
||||
{ "kind": "LOG_PATTERN", "scope": {"appSlug":"orders"}, "level": "ERROR",
|
||||
"pattern": "TimeoutException", "threshold": 5, "windowSeconds": 900 }
|
||||
|
||||
// JVM_METRIC
|
||||
{ "scope": {"appSlug":"orders"}, "metric": "heap_used_percent",
|
||||
{ "kind": "JVM_METRIC", "scope": {"appSlug":"orders"}, "metric": "heap_used_percent",
|
||||
"aggregation": "MAX", "comparator": "GT", "threshold": 90, "windowSeconds": 300 }
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user