Events to ratings DSL
This page is the DSL reference for Event -> Rating policies.
Source of truth used for this document:
- DSL compiler/evaluator:
vluna/oss/packages/vluna-core/src/services/event-to-ratings.dsl.ts - Runtime execution:
vluna/oss/packages/vluna-core/src/features/billing/services/event-to-ratings.service.ts - Supplemental design note in repo:
docs/instructions/event_to_ratings_dsl.md
1) DSL root shape (v1)
{
"dsl_version": "v1",
"engine": "single",
"params": {},
"match": {
"event_type": "demo.outcome"
},
"emit": {
"intents": []
}
}
Required root fields:
dsl_version: must be"v1"engine:"single" | "aggregate"matchemit
Required nested fields:
match.event_typeemit.intents(must be non-empty)
2) Supported syntax summary
2.1 match.event_type
Supported now:
- string exact form:
"job.completed" - object exact form:
{ "op": "exact", "value": "job.completed" }
Not supported by current compiler:
{ "op": "prefix", ... }{ "op": "regex", ... }
Those forms compile with error (not implemented).
2.2 match.where predicate operators
Logical:
{ "all": [predicate, ...] }{ "any": [predicate, ...] }{ "not": predicate }
Comparison:
{ "eq": [lhs, rhs] }{ "ne": [lhs, rhs] }{ "in": [lhs, [rhs...]] }{ "gt": [lhs, rhs] }{ "gte": [lhs, rhs] }{ "lt": [lhs, rhs] }{ "lte": [lhs, rhs] }{ "exists": [ref] }{ "prefix": [lhs, "text"] }{ "contains": [lhs, "text"] }
2.3 Value expressions used by predicates
Supported ref/value forms:
- literals:
null | boolean | number | string { "event": "subject_ref" | "occurred_at" | "billing_account_id" | "event_type" | "semantic_kind" }{ "payload": "a.b.c" }(dot path){ "label": "env" }{ "param": "param_name" }- any
string_expr - any
int_expr
3) emit.intents (rating output)
Each intent emits one internal ingest commit.
Intent shape:
{
"link_kind": "billed",
"feature_code": "svc.job",
"budget_id": { "const": "1001" },
"feature_quantity_minor": { "payload_int": "units", "default": 0 },
"meters": [
{ "meter_code": "svc.job.runtime_ms", "quantity_minor": { "payload_int": "duration_ms", "default": 0 } }
],
"labels": {
"job_id": { "payload_str": "job.id" }
},
"metadata": {
"raw_event_type": { "event_str": "event_type" }
}
}
Constraints enforced by compiler/runtime:
link_kind: optional, defaultbilled; allowedbilled|adjustment|reversal|shadowfeature_code: required slug (^[a-z0-9]+([._-][a-z0-9]+)*$)meters: required non-empty- each
meters[].meter_code: required slug - each
meters[].quantity_minor: requiredint_expr - resolved integer quantities are truncated and clamped to
>= 0
4) Expressions
4.1 int_expr
Supported forms:
- literal number (e.g.
12) { "const": 12 }{ "payload_int": "path", "default": 0 }{ "label_int": "key", "default": 0 }{ "param": "name" }{ "mul": [int_expr, int_expr] }{ "div": [int_expr, int_expr], "rounding": "floor|ceil|nearest" }{ "agg": { ... } }(aggregate engine)
agg form
Supported ops:
countsumavgminmaxcount_distinct
of rules:
count:ofmust not be providedsum|avg|min|max:ofis required, onlypayloadorlabelis allowedcount_distinct:ofis required, supportspayload|label|event
of.event allowed fields:
subject_refoccurred_atbilling_account_idevent_typesemantic_kind
avg supports optional rounding:
floor(default)ceilnearest
Note: compiler computes aggregate key internally (count or op:source:field). A user-provided key is not used.
4.2 string_expr
Supported forms:
- literal string (e.g.
"abc") { "const": "abc" }{ "payload_str": "path", "default": "fallback" }{ "label_str": "key", "default": "fallback" }{ "event_str": "subject_ref" | "event_type" | "billing_account_id" | "semantic_kind" }{ "param": "name" }
4.3 any_expr (used mainly in metadata)
Supported:
- any JSON literal
- nested objects/arrays
- embedded
string_expr/int_expr
5) params and contract-term binding
params defines typed parameters and optional contract source mapping.
Supported param specs:
intstringstring[]bool
Example:
{
"params": {
"min_duration_ms": {
"type": "int",
"min": 0,
"default": 0,
"source": { "term_key": "meeting.success.min_duration_ms" }
},
"allowed_envs": {
"type": "string[]",
"default": ["prod"],
"source": { "term_key": "meeting.success.allowed_envs" }
}
}
}
term_key behavior:
- normalized by
normalizeIdentifier(term_key, 'term_key') - must be 1..128 chars
- allowed charset
[a-z0-9._/@:-]+ - must start/end with
[a-z0-9]
Runtime term lookup:
- reads
contract_termswithkind='e2r_param' - per key picks latest
effective_at <= occurred_at(single) or<= window_start(aggregate)
Resolution priority:
- term value (if present and valid)
- param
default - error
Runtime errors:
contract_term_missingcontract_term_invalid
6) Type coercion and evaluation semantics
6.1 Numeric coercion (toNumberMaybe)
Accepted as number:
- number
- numeric string
- boolean (
true -> 1,false -> 0)
Otherwise => null.
Used in:
payload_int,label_int- numeric predicates (
gt/gte/lt/lte)
6.2 String coercion (toStringMaybe)
Accepted as string:
- string
- number -> string
- boolean ->
"true"/"false"
Otherwise => null.
Used in:
payload_str,label_strprefix,contains
6.3 Equality semantics
eq/ne/in use deep equality via JSON.stringify comparison.
6.4 Null behavior
- policy mismatch returns
null(no intents) - unresolved meter quantity (
null) throws runtime error (meter quantity expression resolved to null) - final integer quantities are truncated and clamped to non-negative
7) Engine input shapes
7.1 engine = single
Input is event-based (source_kind='event') with:
- event fields (
event_type,semantic_kind,occurred_at,subject_ref,billing_account_id) payload- labels map
7.2 engine = aggregate
Input is grouped (source_kind='aggregate') with:
event_type- aggregation window (
window_start,window_end) aggsmap populated by worker
8) Runtime policy-selection behavior
Single-event processing:
- candidate set: active versions
effective_at <= event.occurred_at - filter to same
event_typeandengine='single' - if multiple policies match => processing row becomes
quarantined - if none match and missing-term blockers exist =>
failedwith retry - if none match =>
skipped_no_policy
Aggregate processing:
- candidate set: active versions
effective_at <= window_start - filter to same
event_typeandengine='aggregate' - only exactly one match is accepted; otherwise group is skipped
Semantic-kind guardrails:
- Event-to-Ratings processing expects outcome semantics (
expectedMeterSemanticKind='outcome'in runtime path). - Policy validation also enforces referenced meters to be
semantic_kind='outcome'.
9) Full examples
9.1 Single-event policy
{
"dsl_version": "v1",
"engine": "single",
"params": {
"min_duration_ms": {
"type": "int",
"default": 0,
"source": { "term_key": "meeting.success.min_duration_ms" }
}
},
"match": {
"event_type": "meeting.completed",
"where": {
"all": [
{ "eq": [ { "payload": "status" }, "success" ] },
{ "gte": [ { "payload_int": "duration_ms", "default": 0 }, { "param": "min_duration_ms" } ] }
]
}
},
"emit": {
"intents": [
{
"feature_code": "meeting.success",
"meters": [
{ "meter_code": "meeting.success.count", "quantity_minor": 1 },
{ "meter_code": "meeting.success.duration_ms", "quantity_minor": { "payload_int": "duration_ms", "default": 0 } }
],
"labels": {
"meeting_id": { "payload_str": "meeting_id" },
"env": { "label_str": "env", "default": "unknown" }
}
}
]
}
}
9.2 Aggregate policy
{
"dsl_version": "v1",
"engine": "aggregate",
"match": {
"event_type": "meeting.completed"
},
"emit": {
"intents": [
{
"feature_code": "meeting.daily",
"meters": [
{ "meter_code": "meeting.daily.count", "quantity_minor": { "agg": { "op": "count" } } },
{
"meter_code": "meeting.daily.duration_avg_ms",
"quantity_minor": {
"agg": {
"op": "avg",
"of": { "payload": "duration_ms" },
"default": 0,
"rounding": "nearest"
}
}
}
]
}
]
}
}
10) Authoring checklist
Before creating a policy version:
- use
dsl_version = v1 - choose
engineintentionally (singleoraggregate) - keep
match.event_typeexact - ensure every intent has non-empty
meters - ensure
feature_code/meter_codeare valid slugs - for
params.source.term_key, ensure matchingcontract_terms(kind='e2r_param')exists or provide defaults - validate through policy validate endpoint before activation
11) What is intentionally not supported yet
- event_type prefix/regex matching
- custom aggregate-key names in DSL input
- non-boolean coercion for
boolparams from term values
12) Minimal templates
12.1 Minimal single-engine policy
{
"dsl_version": "v1",
"engine": "single",
"match": {
"event_type": "demo.outcome"
},
"emit": {
"intents": [
{
"feature_code": "demo.outcome",
"meters": [
{
"meter_code": "demo.outcome.count",
"quantity_minor": 1
}
]
}
]
}
}
12.2 Minimal aggregate-engine policy
{
"dsl_version": "v1",
"engine": "aggregate",
"match": {
"event_type": "demo.outcome"
},
"emit": {
"intents": [
{
"feature_code": "demo.outcome.daily",
"meters": [
{
"meter_code": "demo.outcome.daily.count",
"quantity_minor": {
"agg": { "op": "count" }
}
}
]
}
]
}
}
12.3 Minimal params + contract-terms policy
{
"dsl_version": "v1",
"engine": "single",
"params": {
"min_value": {
"type": "int",
"default": 0,
"source": { "term_key": "demo.min_value" }
}
},
"match": {
"event_type": "demo.outcome",
"where": {
"gte": [
{ "payload_int": "value", "default": 0 },
{ "param": "min_value" }
]
}
},
"emit": {
"intents": [
{
"feature_code": "demo.outcome.filtered",
"meters": [
{
"meter_code": "demo.outcome.filtered.count",
"quantity_minor": 1
}
]
}
]
}
}
13) Common errors and fixes
| Error message (real) | Why it happens | Fix |
|---|---|---|
Unsupported dsl.dsl_version: ... | dsl_version is not v1 | Set dsl_version to v1 |
dsl.engine unsupported: ... | engine is not single or aggregate | Use only single or aggregate |
dsl.match.event_type.op not implemented: ... | prefix/regex op used | Use exact match only |
dsl.emit.intents must not be empty | No intents provided | Add at least one intent |
dsl.emit.intents[i].feature_code must be a slug | Invalid feature_code format | Use lowercase slug with [a-z0-9._-] |
dsl.emit.intents[i].meters[j].meter_code must be a slug | Invalid meter_code format | Use lowercase slug with [a-z0-9._-] |
...unsupported int_expr / ...unsupported string_expr | Expression shape not supported | Rewrite using documented expression forms |
...agg.of is required when op=... | Aggregate op requires of | Provide valid of (payload/label/event depending on op) |
...agg.of.event is not allowed for op=... | sum/avg/min/max with event source | Use payload or label source for numeric aggregation |
contract_term_missing: term_key=... param=... | No term value and no default | Add contract term or provide default |
contract_term_invalid: term_key=... param=... expected=... | Term type incompatible with param type | Correct term value type in contract_terms |
meter quantity expression resolved to null | Meter quantity_minor resolves to null at runtime | Ensure expression has valid source or default |
feature_missing / meter_missing (validate result) | DSL references unknown feature/meter | Create missing feature/meter first |
meter_semantic_kind_invalid (validate result) | Referenced meter is not outcome | Use outcome semantic meters for E2R |
feature_meter_unmapped (validate result) | Meter not linked to feature | Add feature-meter mapping |
Recommended debugging order:
- Compile/validate DSL first.
- Confirm feature/meter existence and semantic kind.
- Confirm feature-meter mapping.
- Confirm contract terms availability for all required
params. - Re-check runtime input fields (
payload, labels) used by expressions.