Skip to main content

Features and meters

This page defines the system semantics behind features and meters, beyond endpoint field definitions.

What each concept controls

  • feature_code is the gate/quota axis and the entitlement lookup axis.
  • meter_code is the pricing/settlement axis.
  • feature_family_code is the inheritance/grouping axis for entitlement requirements and family-level entitlements.

Practical implication: a single commit can be accepted or blocked by feature-level policy while being priced by one or more meters.

Code format and normalization

  • feature_code and meter_code are normalized to lowercase.
  • Allowed charset is [a-z0-9._/@:-]+.
  • Length must be 1..128.
  • Code must start and end with [a-z0-9].

Data model invariants

  • Every feature belongs to exactly one feature family.
  • A feature must have exactly one primary meter whose meter_code == feature_code.
  • If you create a feature without explicitly providing meters, the system creates a primary meter automatically (meter_code=feature_code, unit=unit, scale=0, rounding=round).
  • If you provide only non-primary meters, the system infers and adds a primary meter for the feature.

Auto-registry fallback family

  • The system has a fallback feature family: auto.registry.
  • When gate traffic auto-registers an unknown feature (for example, unknown feature_code in authorize/commit flow), that feature can be placed into this fallback family.
  • This fallback family is for temporary landing only. You should move the feature to a business-meaningful family in operations.
  • If not moved, the feature can continue to appear as needing follow-up configuration in management views (for example features/needs-config with missing_price_reason=auto_registry or both).

Semantic kind split: activity vs outcome

  • authorize + commit use activity meters.
  • direct ingest (public) also expects activity.
  • event-to-ratings processing expects outcome.

If an event policy matches but the event semantic kind is not outcome, processing is skipped (not committed).

Feature quantity vs meter quantities

The system intentionally keeps two dimensions:

  • feature-level quantity (quantity_minor): used for lease/quota/rate accounting.
  • meter-level quantities (meters[].quantity_minor): used for pricing line items.

They are not required to be equal.

  • In commit, feature quantity is required and must be greater than 0; quota and rate limits are evaluated against this feature-level quantity dimension.
  • In ingest, feature quantity is optional; when omitted, canonical feature quantity becomes sum(meters[].quantity_minor).

Meter allowlist behavior per feature

Commit/ingest validates each submitted meter against feature-meter mappings for the expected semantic kind.

  • unknown or disallowed meters are treated as invalid for that feature.
  • with auto-registry disabled:
    • ingest returns 422 meter_not_allowed_for_feature.
    • commit is quarantined (no applied quantity/settlement), and returns feature.meter_not_allowed hint.
  • with auto-registry enabled:
    • unknown meters can be auto-created and attached to the feature, then revalidated.

Primary meter fallback rules

When client omits meters[]:

  • commit and ingest use the feature primary meter in the corresponding semantic kind.
  • If no primary meter is available for that semantic kind, treat it as a configuration error and fix the feature-meter mapping.

Create and update rules in management APIs

  • In feature update, a meter code cannot be listed in both meters (upsert) and delete_meters in the same request.
  • Primary meter cannot be deleted.
  • Deleting a feature is soft or hard depending on references:
    • if referenced by plan entitlements, delete becomes soft-delete (active=false);
    • if not referenced, delete removes the feature record.

Default value strategy for omitted fields

Feature create/update

  • name:
    • create: defaults to feature_code when omitted;
    • update: keeps existing value when omitted.
  • description:
    • create: defaults to empty string;
    • update: keeps existing value when omitted.
  • active:
    • create: defaults to true;
    • update: keeps existing value when omitted.
  • entitlement_required:
    • create: omitted means unset at feature level (family-level rule applies);
    • update: keeps existing value when omitted.
  • metadata:
    • create: defaults to {};
    • update: keeps existing value when omitted.
  • meters:
    • create/update omitted: feature keeps its current mapping; for brand-new feature, system ensures a primary meter exists.

Meter create/update

  • semantic_kind: defaults to activity when omitted on create; keeps existing value on update.
  • unit: defaults to unit on create; keeps existing value on update.
  • scale: defaults to 0 on create; keeps existing value on update.
  • rounding (meter-level): defaults to round on create; keeps existing value on update.
  • active: defaults to true on create; keeps existing value on update.
  • metadata: defaults to {} on create; keeps existing value on update when omitted.

Meter pricing defaults (when meter_prices is provided but fields are omitted)

  • unit_cost_xusd: defaults to 0.
  • unit_price_base_xusd: if omitted, derived from unit_cost_xusd.
  • unit_price_dynamic_xusd: defaults to 0.
  • unit_price_xusd: if omitted, derived from base + dynamic.
  • unit_quantity_minor / cost_unit_quantity_minor: if one side omitted, backfilled from the other side; if both omitted, default 1.
  • rounding / cost_rounding:
    • if one side omitted, backfilled from the other side;
    • if both omitted, default nearest.
  • effective_at: defaults to current time when omitted.

Pricing behavior that affects feature/meter modeling

  • Pricing is resolved at commit/ingest time from latest meter_prices row per meter (effective_at descending).
  • Contract pricing terms can adjust base meter price, but only when an active meter_prices row exists for that meter at the same time point.
  • If a contract term exists but base meter price row is missing, contract term is ignored and warning hint pricing.meter_price_missing is emitted.
  • Missing meter pricing in commit causes quarantined application status and pricing.not_configured hint; line snapshots are still materialized with zeroed "missing" provenance.
  • Residual rounding is per billing account + meter + pricing identity; meter metadata residual_mode='prepaid' changes rounding behavior from postpaid residual carry to prepaid carry.

Entitlement resolution linked to features/families

  • Effective entitlement_required is resolved as:
    • feature override if set
    • else fallback to feature-family entitlement_required.
  • Plan entitlements can target:
    • exact feature
    • feature family
    • wildcard (both null), then specificity/priority resolution applies.
  • Missing entitlement returns ENTITLEMENT.REQUIRED; explicit deny returns ENTITLEMENT.DENIED.

Event -> ratings DSL coupling to feature/meter model

DSL outputs follow these constraints:

  • emit.intents[].feature_code and meters[].meter_code must be slug format.
  • match.event_type currently supports exact matching only.
  • Each emitted intent becomes one ingest commit in the billing engine.
  • Intent feature_quantity_minor is optional:
    • present: written as feature quantity.
    • absent: feature quantity derived from meter sum during ingest.

Design implication: DSL is not only mapping logic; it must target valid feature-meter topology (including semantic kind and allowlisted meters), or intents will fail/quarantine at ingest.

Next