Skip to content

Caliper aggregate measures + membership status (closing CAL-VOCAB-2)

Status: accepted (increments ADR-0041; governed by ADR-0042)

CAL-VOCAB-2 is the last Caliper vocabulary requirement still todo that is buildable without the held emission worker host. It binds three controlled vocabularies: an AggregateMeasure metric is a Caliper metric term, an entity status is Active or Inactive, and a sensor declares the metric profiles it supports as Caliper profile terms. CAL-VOCAB-1 already guards the Action vocabulary; this closes the Metric / Status / Profile vocabularies.

  1. AggregateMeasures are emitted as standalone entity describes, not events. No Caliper event type accepts an AggregateMeasure in generated (e.g. ToolUseEvent defines no generated entity), and the spec models aggregate measures as AggregateMeasure entities in an Envelope’s data[]. So the rail gains a thin entity-describe emission path: a CaliperWireDataItem = CaliperWireEvent | CaliperWireDescribe union flows through the same outbox → Envelope → transport the events use (the worker already POSTs data[] from generic outbox wirePayloads — only types widen).
  2. The measures are de-identified package aggregates. Each describe is a COUNT across learners for one external-package assignment (UnitsCompleted, UnitsPassed), aggregated from package_progress GROUP BY assignment — carrying no person. So they are anonymous statistics: outside ADR-0042’s pseudonymous-actor erasure path entirely (nothing to sever), unlike the per-learner event rail.
  3. Status rides the existing Membership. status is, in the Caliper 1.2 bootcamp model, a property only of Membership — which we already emit (CAL-VOCAB-3). The reader maps offering_membership.membership_statusActive (active) / Inactive (any other), so the status vocabulary is honoured on a person-linked entity that the event rail already erases.
  4. Done = the interop lane. CAL-VOCAB-2 flips when the conformance lane proves all three: an emitted AggregateMeasure.metric ∈ the Metric vocabulary, a Membership.status ∈ {Active, Inactive}, and every emitted event profile ∈ the Profile vocabulary (a drift guard like CAL-VOCAB-1’s for actions).
  • AggregateMeasure on a ToolUseEvent’s generated. Rejected: the textual profile rule forbids any generated on ToolUseEvent; the spec carries aggregate measures as entity describes.
  • Per-learner AggregateMeasures. Rejected as the default: a single learner’s metric is personal data needing the pseudonymous-actor erasure linkage, which an AggregateMeasure entity has no slot for. De-identified package aggregates are both the truer meaning of “aggregate” and erasure-free.
  • A synthetic status on an invented entity. Rejected: Membership is the spec’s status carrier and we already emit it — no new surface needed.
  • The outbox/Envelope/transport types widen to CaliperWireDataItem; the snapshot of package aggregates is collected by a GROUP-BY query (collectPackageMetricsSnapshot) and enqueued as describes — built and idle until the held worker host runs it (the periodic metrics-snapshot push is part of the host’s job, exactly like the event rail is built-but-idle).
  • CAL-VOCAB-2 → done; the Caliper board is 23 of 24. Only CAL-SND-1 remains, gated on the deployed-endpoint TLS smoke check (the held host).
  • Periodic-snapshot refinement (ADR-0049): a metric’s metricValue changes over time, but the outbox dedupes on eventId, so keying it on the measure IRI would emit each aggregate exactly once and freeze the value. The worker therefore enqueues each snapshot run under a run-scoped outbox identity (<iri>#<runId>) while keeping the wire AggregateMeasure.id the stable metric IRI — the Endpoint receives a sendTime-differentiated time series. Omitting runId keeps the once-only IRI identity (the conformance lane’s single current-value describe).