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:
- Labels are targets, never inputs. Role separation is structural — a label value cannot reach the model's input through any code path.
- 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 kind | Shape | Use when |
|---|---|---|
label_table | a table/CSV with entity_id, value, observed_at columns | outcomes live in your warehouse (the quickstart does this) |
label_events | outcomes arrive as events on the stream | your systems already emit outcome events |
derived | the platform derives outcomes from the entity's own future events | zero labeling effort — see the label-free quickstart |
Next: Leakage gates — what stops label information from sneaking into inputs.