Skip to content

Caliper as a second analytics rail via a shared telemetry projection seam

Status: accepted (reverses the Caliper deferral in ADR-0007 and the deferred tag in standards.md)

We will implement full IMS Caliper Analytics 1.2 conformance in both roles — emergent becomes a Caliper Sensor (emits Envelopes/Events) and a Caliper Endpoint (ingests them) — before launch. This reverses the prior decision to defer Caliper “until a named ecosystem consumer requires it”: Product now wants maximal standards coverage, and we treat each conformance gap as a product capability the standard surfaced, not a badge to game.

Caliper and xAPI solve the same problem (emit learning telemetry) with parallel wire models, so the engineering stance is to make Caliper cheap and derived, never a second instrumentation pass or a second source of truth.

  1. Both roles. Sensor + Endpoint. The Endpoint extends apps/lrs into a dual-protocol telemetry store (reusing its Hono server, client-credential auth, and storage), persisting Envelopes/Events to a new caliper.* schema — it does not duplicate the LRS.
  2. One shared projection seam — emits Caliper and xAPI. A new Telemetry Projection reads canonical state/event streams (interaction_event, assessment_attempt, document_progress_event, discussion post/thread, gradebook, LTI launch, catalog_resource) and fans out through two wire adapters. emergent gains a first xAPI emitter here (today the LRS only receives). The projection is read-only over canonical state — consistent with ADR-0007’s state-canonical, not event-sourced, model.
  3. All 15 metric profiles. We claim every Caliper profile, building the missing surfaces (Media, Search, Survey) as real product features — each designed in its own session and landing a thin event-stream contract the rail consumes. A profile is claimed only where a genuine surface emits it.
  4. Schemas are conform-ed’s. The Caliper (and xAPI) wire contracts live in @conform-ed/contracts (building on the existing Bootcamp-derived schemas and CALIPER_TEXTUAL_EVENT_RULES); emergent owns only the domain→wire mapping and the value-set drift guard. conform-ed’s Caliper board keeps the 8 structural MUSTs and adds Sender and Endpoint role profiles; per-profile event coverage is tracked on the information-model axis.
  5. Live, async, batched, self-dogfooded transport. A worker batches Events into Envelopes (data[]) and POSTs them (bearer auth, at-least-once via the existing interaction_event idempotency keys) to configured endpoints — defaulting to our own Caliper endpoint, and the xAPI emitter to our own LRS — so both roles run as one real loop.
  6. Done = internal lane + dossier. test:caliper:conformance validates emitted Envelopes/Events against conform-ed’s official schema and the endpoint’s accept-valid / reject-malformed behaviour, exactly like OB/CLR/QTI. The 1EdTech Caliper certification submission is a separate, later milestone, not a blocker for the board.

Governance of identity on the wire (pseudonymous-by-default, consent, erasure) is decided separately in ADR-0042.

  • xAPI-only (status quo). Rejected: Product wants Caliper coverage, and the deferral rationale (“no consumer yet”) no longer governs.
  • Caliper as a translation of LRS-held xAPI. Rejected: those statements come from external tools/cmi5, not emergent’s own activity, and bind us to a non-normative mapping.
  • Native per-surface Caliper emission. Rejected: duplicates the event log and scatters telemetry logic across apps, contradicting ADR-0007.
  • A new caliper.* schema and erasure-registry declarations (one migration batch, Anton’s domain), plus the dual-protocol rename/extension of apps/lrs.
  • The xAPI emitter is new scope riding along; kept lean by reusing the LRS’s StatementV2Schema.
  • conform-ed Caliper contract completion (currently 78/1957 modelled) is the long pole, but the Bootcamp schemas + textual event rules already exist — it is wiring + value-set verification.
  • The all-not-applicable Caliper overlay is retired in favour of real done/role entries.
  • The Sensor emission worker host is an emergent/consumer concern (it reads emergent’s canonical tables + caliper.* and uses emergent infra) — never conform-ed, which ships contracts only. The spec-shaped transport (sendCaliperEnvelope + version negotiation) could one day be a conform-ed reference helper, but with no second consumer it stays in emergent for now (revisit if another Caliper sensor appears). runEmissionPass / buildEmissionPassConfig are kept host-agnostic so the host slots in later with no rework.
  • Sequencing (2026-06-26): the worker host is held and batched with the Media/Survey + AggregateMeasure features. The two remaining todos are both deployment/feature-gated — CAL-SND-1 flips under a deployed-endpoint smoke check (needs the host); CAL-VOCAB-2 flips when those metric-bearing surfaces emit. Standing up the host once, after those land, runs the worker against the complete surface and closes both in one push → board 22→24. The full read-only projection rail (7 readers, governance, erasure, config-time TLS guard) is already built and idle until then.
  • Host RESOLVED (2026-06-27) by ADR-0049: a dedicated, single-replica, long-lived Bun Deployment running runEmissionPass in a self-pacing adaptive loop (not a cron), POSTing to our own LRS Caliper Endpoint. Spin/SpinKube was explored first and shelved (the SpinKube path would fork the Drizzle stack + the conformance-proven transport). CAL-SND-1 stays todo only until an external https endpoint + deployed smoke check exist.
  • The two deferred metric-profile surfaces are now built as real product features: ADR-0045 (Media → MediaProfile) and ADR-0046 (Survey → SurveyProfile). They declare + emit the profiles but do not flip CAL-VOCAB-2 on their own — that still awaits the separate AggregateMeasure metric + Active/Inactive status follow-up, after which the held host push closes it.