Skip to main content

Labels and masking

A label is a customer-defined outcome the model learns to predict: churned or not, converted or not, hours to delivery. Two principles govern everything about them:

  1. Labels are targets, never inputs. Role separation is structural — a label value cannot reach the model's input through any code path.
  2. Unobserved is never negative. The absence of a label row means "we don't know", and unknown outcomes are masked out of training, not assumed to be "no".

A worked example: streaming subscription churn​

A streaming service tracks viewer entities (playback.start, playback.stop, subscription.renewed). They want to predict 30-day churn. Each month, for each account whose renewal window has closed, their billing system knows the outcome — so they ship label observations:

entity_id,value,observed_at
v_00481,true,2026-03-01T00:00:00Z
v_00522,false,2026-03-01T00:00:00Z

Three fields, and every one carries meaning:

  • entity_id — which viewer this outcome belongs to.
  • value — the outcome itself.
  • observed_at — when the outcome became known. Not when the CSV was exported, not when it was uploaded: when the fact existed in the world.

Why observed_at is sacred​

Training supervises the model at a moment in time: "given everything known about v_00481 up to time t, predict the outcome." The observation attaches to the viewer's last event at or before observed_at; the model's view at that point contains only earlier events. Point-in-time correctness is enforced by the dataset builder — a label observed on March 1 can never supervise a model state that already contains March 2 behavior.

Maturation: don't grade what hasn't happened​

Maturation (maturation_days) says how much time must separate the model state being supervised from the observation. With maturation_days: 30, an observation only trains states at least 30 days older than observed_at — the state was making a genuine 30-day-ahead prediction. States inside the window are masked (reason: immature), because at those moments the outcome was already partially determined: a viewer who cancelled on day 29 would hand the model a giveaway.

Unobserved ≠ negative​

The billing system exports outcomes only for accounts that reached their renewal date. Viewers who joined last week have no row — and that absence means "outcome unknown", not "did not churn". Tessera masks them.

There is one explicit, guarded exception: opting into assume_negative_after_maturation treats post-maturation absence as a negative — and the build fails if the resulting positive rate falls below your guard_min_positive_rate, because a silently broken label join (a renamed id column, a truncated export) looks exactly like "everyone stopped churning".

Every masked target is accounted for​

The dataset manifest reports supervision accounting per label: how many observations attached, and how many were masked with which reason (immature, no_supervision_event, after_snapshot, unparseable). GET /labels/{id}/stats shows observation counts, the observed-at range, and maturation coverage before you ever build. The evaluation suite independently recomputes every mask (gate G5) — a training run that used a future-dated target cannot be promoted.

Three ways to ship labels​

Source kindShapeUse when
label_tablea table/CSV with entity_id, value, observed_at columnsoutcomes live in your warehouse (the quickstart does this)
label_eventsoutcomes arrive as events on the streamyour systems already emit outcome events
derivedthe platform derives outcomes from the entity's own future eventszero labeling effort — see the label-free quickstart

Next: Leakage gates — what stops label information from sneaking into inputs.