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.
The decisions
Section titled “The decisions”- 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. - One shared projection seam — emits Caliper and xAPI. A new Telemetry Projection
reads canonical state/event streams (
interaction_event,assessment_attempt,document_progress_event, discussionpost/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. - 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.
- Schemas are conform-ed’s. The Caliper (and xAPI) wire contracts live in
@conform-ed/contracts(building on the existing Bootcamp-derived schemas andCALIPER_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. - Live, async, batched, self-dogfooded transport. A worker batches Events into Envelopes
(
data[]) and POSTs them (bearer auth, at-least-once via the existinginteraction_eventidempotency 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. - Done = internal lane + dossier.
test:caliper:conformancevalidates 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.
Considered alternatives
Section titled “Considered alternatives”- 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.
Consequences
Section titled “Consequences”- 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/buildEmissionPassConfigare 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
runEmissionPassin 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 staystodoonly 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.