{
  "openapi": "3.1.0",
  "info": {
    "title": "Alpha Events",
    "description": "The Alpha Events API is the school-language facade over the Caliper learning-activity ledger. Activity events are read-only projections that rename Caliper fields to school terms. Ingest uses submitEvents and importSourceEvents; the ledger is immutable with no effective dating. Uses cursor pagination, flat envelopes, and RFC 7807 errors.",
    "version": "1.0"
  },
  "tags": [
    {
      "name": "Activity event",
      "description": "The plain-language Alpha read surface over the Caliper event ledger. Projects `caliper:caliperEvent` into school words with the four events-owned extends columns (`kind`, `studentId`, `contentId`, `orgId`) set once at ingest. Read-only; row-restricted to processed envelopes."
    },
    {
      "name": "Activity source",
      "description": "The plain-language Alpha read surface over registered Caliper sensors, cutting credential secrets. Read-only; activity sources are registered out-of-band on the Caliper admin surface."
    },
    {
      "name": "Event batch",
      "description": "The plain-language Alpha read surface over Caliper envelope receipts, renaming transport fields into school words and cutting raw payloads and version constants. Read-only; batches are created by the ingest pipeline, never through projection CRUD."
    },
    {
      "name": "Source import batch",
      "description": "The ETL receipt entity for importSourceEvents: records adapter dispatch, ingest counts, quarantine refs, and processing status for one source-generic import invocation. PK is the client-supplied or server-issued sourceImportId (the idempotency anchor). Status endpoint is the plain CRUD GET — no async/queue machinery."
    }
  ],
  "paths": {
    "/events/v1/events": {
      "get": {
        "operationId": "listActivityEvents",
        "summary": "List activity events",
        "description": "Lists activity events visible through the projection. Supports equality filters on typed refs and kind/action/caliperType, range filters on occurredAt, and modifiedSince polling on updatedAt.\n\n> **Requires role:** `system` · `teacher` · `student` · `parent`",
        "tags": [
          "Activity event"
        ],
        "parameters": [
          {
            "name": "studentId",
            "in": "query",
            "required": false,
            "description": "Filter by typed student relationship ref.",
            "schema": {
              "type": "string"
            },
            "x-filter": {
              "column": "studentId",
              "operator": "eq"
            }
          },
          {
            "name": "contentId",
            "in": "query",
            "required": false,
            "description": "Filter by typed content relationship ref.",
            "schema": {
              "type": "string"
            },
            "x-filter": {
              "column": "contentId",
              "operator": "eq"
            }
          },
          {
            "name": "orgId",
            "in": "query",
            "required": false,
            "description": "Filter by typed org relationship ref.",
            "schema": {
              "type": "string"
            },
            "x-filter": {
              "column": "orgId",
              "operator": "eq"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "Filter by governed Alpha activity kind.",
            "schema": {
              "type": "string",
              "enum": [
                "app_opened",
                "content_viewed",
                "video_started",
                "video_scrubbed",
                "hint_requested",
                "question_answered",
                "assessment_started",
                "assessment_submitted",
                "lesson_finished",
                "search_run",
                "feedback_viewed",
                "session_joined"
              ]
            },
            "x-filter": {
              "column": "kind",
              "operator": "eq"
            }
          },
          {
            "name": "action",
            "in": "query",
            "required": false,
            "description": "Filter by Caliper action vocabulary value.",
            "schema": {
              "type": "string"
            },
            "x-filter": {
              "column": "action",
              "operator": "eq"
            }
          },
          {
            "name": "caliperType",
            "in": "query",
            "required": false,
            "description": "Filter by Caliper event type.",
            "schema": {
              "type": "string"
            },
            "x-filter": {
              "column": "caliperType",
              "operator": "eq"
            }
          },
          {
            "name": "happenedAtFrom",
            "in": "query",
            "required": false,
            "description": "Inclusive lower bound on occurredAt (range filter).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "x-filter": {
              "column": "occurredAt",
              "operator": "gte"
            }
          },
          {
            "name": "happenedAtTo",
            "in": "query",
            "required": false,
            "description": "Inclusive upper bound on occurredAt (range filter).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "x-filter": {
              "column": "occurredAt",
              "operator": "lte"
            }
          },
          {
            "name": "modifiedSince",
            "in": "query",
            "required": false,
            "description": "Return only events with updatedAt >= the supplied timestamp (data-freshness polling).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "x-filter": {
              "column": "updatedAt",
              "operator": "gte"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return on this page (clamped server-side).",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor — pass the previous page's `nextCursor` to fetch the next page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ActivityEvent"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor for the next page, or null when this is the last page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "submitEvents",
        "summary": "Submit activity events in school language",
        "description": "The school-language Alpha ingest door. Accepts a batch of events in plain school words, derives the Caliper (type, profile, action) tuple from the governed kind_map policy, resolves typed refs, enforces the event-result boundary, and writes caliperEvent master + extends columns in one transaction.\n\n> **Requires role:** `system` · `source_writer` · `adapter`",
        "tags": [
          "Activity event"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "The idempotency key (max 128 chars). Optional — when present, the call is replay-protected; a completed replay returns the stored response at 200 with idempotency-replayed: true.",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "sentAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Optional client-side send timestamp for the batch."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "app_opened",
                            "content_viewed",
                            "video_started",
                            "video_scrubbed",
                            "hint_requested",
                            "question_answered",
                            "assessment_started",
                            "assessment_submitted",
                            "lesson_finished",
                            "search_run",
                            "feedback_viewed",
                            "session_joined"
                          ],
                          "description": "The governed Alpha activity kind. Maps to a Caliper (eventType, profile, action) tuple via the kind_map policy."
                        },
                        "studentId": {
                          "type": "string",
                          "description": "Typed student ref — resolved to oneroster:user.sourcedId."
                        },
                        "contentId": {
                          "type": "string",
                          "description": "Typed content ref — hook-validated loose ref to content:contentItem."
                        },
                        "orgId": {
                          "type": "string",
                          "description": "Typed org ref — resolved to oneroster:org.sourcedId."
                        },
                        "occurredAt": {
                          "type": "string",
                          "format": "date-time",
                          "description": "The instant the event occurred."
                        },
                        "activitySourceId": {
                          "type": "string",
                          "description": "Optional activity source sensor IRI for envelope grouping."
                        },
                        "eventType": {
                          "type": "string",
                          "description": "Optional caller-supplied Caliper event type — must match kind_map or 422."
                        },
                        "profile": {
                          "type": "string",
                          "description": "Optional caller-supplied Caliper profile — must match kind_map or 422."
                        },
                        "action": {
                          "type": "string",
                          "description": "Optional caller-supplied Caliper action — must match kind_map or 422."
                        },
                        "object": {
                          "type": "object",
                          "description": "Optional object JSON-LD payload for the event.",
                          "additionalProperties": true
                        },
                        "target": {
                          "type": "object",
                          "description": "Optional target JSON-LD payload.",
                          "additionalProperties": true
                        },
                        "extensions": {
                          "type": "object",
                          "description": "Optional app-specific extension payload.",
                          "additionalProperties": true
                        }
                      },
                      "required": [
                        "kind",
                        "occurredAt"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1,
                    "description": "The batch of activity events to ingest."
                  }
                },
                "required": [
                  "events"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK (idempotent replay — the stored first response; idempotency-replayed: true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityEvent"
                }
              }
            },
            "headers": {
              "idempotency-replayed": {
                "description": "true when this response is a stored idempotent replay (the hook did not run).",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityEvent"
                }
              }
            },
            "headers": {
              "idempotency-replayed": {
                "description": "true when this response is a stored idempotent replay (the hook did not run).",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — an operation with this Idempotency-Key is already in progress (60s lock).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Problem (alpha_event_kind).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "428": {
            "description": "Precondition Required — this operation requires the Idempotency-Key header.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/events/{eventIri}": {
      "get": {
        "operationId": "getActivityEvent",
        "summary": "Get an activity event",
        "description": "Returns one activity event by its event IRI.\n\n> **Requires role:** `system` · `teacher` · `student` · `parent`",
        "tags": [
          "Activity event"
        ],
        "parameters": [
          {
            "name": "eventIri",
            "in": "path",
            "required": true,
            "description": "The event IRI (urn:uuid:...).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivityEvent"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Problem (activity_event_not_found).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/activity-sources": {
      "get": {
        "operationId": "listActivitySources",
        "summary": "List activity sources",
        "description": "Lists registered activity sources visible through the projection. Supports equality filter on status.\n\n> **Requires role:** `system` · `teacher`",
        "tags": [
          "Activity source"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by operational status.",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "paused",
                "retired"
              ]
            },
            "x-filter": {
              "column": "status",
              "operator": "eq"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return on this page (clamped server-side).",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor — pass the previous page's `nextCursor` to fetch the next page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ActivitySource"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor for the next page, or null when this is the last page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/activity-sources/{sensorIri}": {
      "get": {
        "operationId": "getActivitySource",
        "summary": "Get an activity source",
        "description": "Returns one activity source by its sensor IRI identifier.\n\n> **Requires role:** `system` · `teacher`",
        "tags": [
          "Activity source"
        ],
        "parameters": [
          {
            "name": "sensorIri",
            "in": "path",
            "required": true,
            "description": "The activity source identifier (sensor IRI).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ActivitySource"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Problem (activity_source_not_found).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/event-batches": {
      "get": {
        "operationId": "listEventBatches",
        "summary": "List event batches",
        "description": "Lists event batch receipts visible through the projection. Supports equality filters on activitySourceId and status.\n\n> **Requires role:** `system` · `teacher`",
        "tags": [
          "Event batch"
        ],
        "parameters": [
          {
            "name": "activitySourceId",
            "in": "query",
            "required": false,
            "description": "Filter by the sending activity source.",
            "schema": {
              "type": "string"
            },
            "x-filter": {
              "column": "activitySourceId",
              "operator": "eq"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by batch processing status.",
            "schema": {
              "type": "string",
              "enum": [
                "received",
                "processed",
                "rejected",
                "duplicate"
              ]
            },
            "x-filter": {
              "column": "status",
              "operator": "eq"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return on this page (clamped server-side).",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor — pass the previous page's `nextCursor` to fetch the next page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/EventBatch"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor for the next page, or null when this is the last page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/event-batches/{envelopeHash}": {
      "get": {
        "operationId": "getEventBatch",
        "summary": "Get an event batch",
        "description": "Returns one event batch receipt by its content-address identifier.\n\n> **Requires role:** `system` · `teacher`",
        "tags": [
          "Event batch"
        ],
        "parameters": [
          {
            "name": "envelopeHash",
            "in": "path",
            "required": true,
            "description": "The batch identifier (sha256: content address).",
            "schema": {
              "type": "string",
              "pattern": "^sha256:[0-9a-f]{64}$"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventBatch"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Problem (event_batch_not_found).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/source-imports": {
      "get": {
        "operationId": "listSourceImportBatches",
        "summary": "List source import batch receipts",
        "description": "Returns all source import batch receipts for the authenticated tenant, ordered by most recent first.\n\n> **Requires role:** `system` · `adapter`",
        "tags": [
          "Source import batch"
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by batch processing status.",
            "schema": {
              "type": "string",
              "enum": [
                "received",
                "processed",
                "rejected",
                "partial"
              ]
            },
            "x-filter": {
              "column": "status",
              "operator": "eq"
            }
          },
          {
            "name": "adapterId",
            "in": "query",
            "required": false,
            "description": "Filter by the source adapter that processed the batch.",
            "schema": {
              "type": "string",
              "enum": [
                "timeback_learning_event_v1",
                "timeback_caliper_raw_v1"
              ]
            },
            "x-filter": {
              "column": "adapterId",
              "operator": "eq"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of items to return on this page (clamped server-side).",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor — pass the previous page's `nextCursor` to fetch the next page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "nextCursor"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SourceImportBatch"
                      }
                    },
                    "nextCursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Cursor for the next page, or null when this is the last page."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "importSourceEvents",
        "summary": "Import source events via adapter dispatch",
        "description": "The source-generic ETL door. Dispatches rows through the registered adapter normalizer, rejects results-owned rows toward the results import door, quarantines failing rows (never coerces), and writes the same caliperEvent+extends fan-out as submitEvents for accepted rows. Writes the sourceImportBatch receipt with counts.\n\n> **Requires role:** `system` · `adapter`",
        "tags": [
          "Source import batch"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "The idempotency key (max 128 chars). Optional — when present, the call is replay-protected; a completed replay returns the stored response at 200 with idempotency-replayed: true.",
            "schema": {
              "type": "string",
              "maxLength": 128
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "adapterId": {
                    "type": "string",
                    "enum": [
                      "timeback_learning_event_v1",
                      "timeback_caliper_raw_v1"
                    ],
                    "description": "The registered source-adapter shape to dispatch through."
                  },
                  "importId": {
                    "type": "string",
                    "format": "uuid",
                    "description": "Optional client-supplied import batch ID for idempotency anchoring."
                  },
                  "dryRun": {
                    "type": "boolean",
                    "description": "When true, validates and classifies rows but writes no caliperEvent records."
                  },
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "description": "One source row — shape varies by adapter.",
                      "additionalProperties": true
                    },
                    "minItems": 1,
                    "description": "The source rows to ingest through the adapter normalizer."
                  }
                },
                "required": [
                  "adapterId",
                  "rows"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceImportBatch"
                }
              }
            },
            "headers": {
              "idempotency-replayed": {
                "description": "true when this response is a stored idempotent replay (the hook did not run).",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true",
                    "false"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not Found.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict — an operation with this Idempotency-Key is already in progress (60s lock).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Problem (adapter_validation_failed).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "428": {
            "description": "Precondition Required — this operation requires the Idempotency-Key header.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/events/v1/source-imports/{sourceImportId}": {
      "get": {
        "operationId": "getSourceImportBatch",
        "summary": "Get a source import batch receipt (the status endpoint)",
        "description": "Returns a single source import batch receipt by its sourceImportId. This is the receipt-is-an-entity status endpoint: the importSourceEvents command command writes the receipt, and this plain CRUD GET serves the batch status.\n\n> **Requires role:** `system` · `adapter`",
        "tags": [
          "Source import batch"
        ],
        "parameters": [
          {
            "name": "sourceImportId",
            "in": "path",
            "required": true,
            "description": "The source import batch identifier.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SourceImportBatch"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Problem (source_import_batch_not_found).",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "Conflict.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal Server Error.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "501": {
            "description": "Not Implemented.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ActivityEvent": {
        "type": "object",
        "properties": {
          "eventIri": {
            "type": "string",
            "pattern": "^urn:uuid:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$",
            "description": "The event's stable IRI, passed through as the projection's primary identifier. The wire name 'id' is inherited from the base entity's x-wire-name."
          },
          "kind": {
            "type": "string",
            "enum": [
              "app_opened",
              "content_viewed",
              "video_started",
              "video_scrubbed",
              "hint_requested",
              "question_answered",
              "assessment_started",
              "assessment_submitted",
              "lesson_finished",
              "search_run",
              "feedback_viewed",
              "session_joined"
            ],
            "description": "The governed Alpha activity kind, computed at write time from the Caliper (type, profile, action) tuple via the kind_map policy. Never inferred at read time.",
            "x-owned-by": "events"
          },
          "caliperType": {
            "type": "string",
            "enum": [
              "AnnotationEvent",
              "AssessmentEvent",
              "AssessmentItemEvent",
              "AssignableEvent",
              "FeedbackEvent",
              "ForumEvent",
              "GradeEvent",
              "MediaEvent",
              "MessageEvent",
              "NavigationEvent",
              "QuestionnaireEvent",
              "QuestionnaireItemEvent",
              "ResourceManagementEvent",
              "SearchEvent",
              "SessionEvent",
              "SurveyEvent",
              "SurveyInvitationEvent",
              "ThreadEvent",
              "ToolLaunchEvent",
              "ToolUseEvent",
              "ViewEvent"
            ],
            "description": "The Caliper 1.2 event type vocabulary value, renamed from eventType for the Caliper-literate reader."
          },
          "action": {
            "type": "string",
            "enum": [
              "Abandoned",
              "Accepted",
              "Activated",
              "Added",
              "Archived",
              "Attached",
              "Bookmarked",
              "ChangedResolution",
              "ChangedSize",
              "ChangedSpeed",
              "ChangedVolume",
              "Classified",
              "ClosedPopout",
              "Commented",
              "Completed",
              "Copied",
              "Created",
              "Deactivated",
              "Declined",
              "Deleted",
              "Described",
              "DisabledClosedCaptioning",
              "Disliked",
              "Downloaded",
              "EnabledClosedCaptioning",
              "Ended",
              "EnteredFullScreen",
              "ExitedFullScreen",
              "ForwardedTo",
              "Graded",
              "Hid",
              "Highlighted",
              "Identified",
              "JumpedTo",
              "Launched",
              "Liked",
              "Linked",
              "LoggedIn",
              "LoggedOut",
              "MarkedAsRead",
              "MarkedAsUnread",
              "Modified",
              "Muted",
              "NavigatedTo",
              "OpenedPopout",
              "OptedIn",
              "OptedOut",
              "Paused",
              "Posted",
              "Printed",
              "Published",
              "Questioned",
              "Ranked",
              "Recommended",
              "Removed",
              "Reset",
              "Restarted",
              "Restored",
              "Resumed",
              "Retrieved",
              "Returned",
              "Reviewed",
              "Rewound",
              "Saved",
              "Searched",
              "Sent",
              "Shared",
              "Showed",
              "Skipped",
              "Started",
              "Submitted",
              "Subscribed",
              "Tagged",
              "TimedOut",
              "Unmuted",
              "Unpublished",
              "Unsubscribed",
              "Uploaded",
              "Used",
              "Viewed"
            ],
            "description": "The Caliper Action vocabulary value, passed through unchanged."
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time",
            "description": "The instant the event occurred, renamed from eventTime to plain language."
          },
          "studentId": {
            "type": "string",
            "minLength": 1,
            "description": "Typed student relationship ref, set at ingest from the resolved actor identity. A tenant-carrying composite FK to oneroster:user.",
            "x-owned-by": "events"
          },
          "contentId": {
            "type": "string",
            "minLength": 1,
            "description": "Typed content relationship ref, set at ingest. Hook-validated loose ref to the content module's contentItem — NOT a DB FK per.",
            "x-owned-by": "events"
          },
          "orgId": {
            "type": "string",
            "minLength": 1,
            "description": "Typed org relationship ref, set at ingest from the resolved org identity. A tenant-carrying composite FK to oneroster:org.",
            "x-owned-by": "events"
          },
          "actor": {
            "oneOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ],
            "description": "The actor reference from the Caliper payload. Restricted: the projection surfaces resolved identity refs only (typed studentId replaces IRI parsing); raw JSON-LD sub-keys beyond {id, type} are not guaranteed stable across versions."
          },
          "object": {
            "oneOf": [
              {
                "type": "string",
                "minLength": 1
              },
              {
                "type": "object",
                "additionalProperties": true
              }
            ],
            "description": "The object reference from the Caliper payload. Restricted: the projection surfaces resolved identity refs only (typed contentId replaces IRI parsing); raw JSON-LD sub-keys beyond {id, type} are not guaranteed stable across versions."
          },
          "profile": {
            "type": "string",
            "enum": [
              "AnnotationProfile",
              "AssessmentProfile",
              "AssignableProfile",
              "FeedbackProfile",
              "ForumProfile",
              "GradingProfile",
              "MediaProfile",
              "ReadingProfile",
              "ResourceManagementProfile",
              "SearchProfile",
              "SessionProfile",
              "SurveyProfile",
              "ToolLaunchProfile",
              "ToolUseProfile",
              "GeneralProfile"
            ],
            "description": "The Caliper Profile value for this event."
          },
          "envelopeStatus": {
            "type": "string",
            "enum": [
              "processed"
            ],
            "description": "Row filter: only events on processed envelopes are visible through this projection. The restrict emits a WHERE envelopeStatus = 'processed' in the security_invoker view.",
            "readOnly": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Server-managed row-creation timestamp (RFC 3339). Read-only — never accepted on create or update."
          }
        },
        "required": [
          "eventIri",
          "caliperType",
          "action",
          "occurredAt",
          "actor",
          "object",
          "envelopeStatus",
          "createdAt"
        ]
      },
      "ActivitySource": {
        "type": "object",
        "properties": {
          "sensorIri": {
            "type": "string",
            "minLength": 1,
            "description": "The activity source's stable IRI, serving as the primary identity field (the resource id). Composite-natural PK constraint requires pass-through naming; the design-spec wire alias to 'id' is achieved via x-wire-name on the caliper base."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "description": "Human-readable name for the activity source, renamed from displayName to plain language."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused",
              "retired"
            ],
            "description": "Operational status of the activity source: active sources may submit events, paused are temporarily disabled, retired are kept for historical attribution.",
            "default": "active"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Server-managed row-creation timestamp (RFC 3339). Read-only — never accepted on create or update."
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Server-managed last-update timestamp (RFC 3339). Read-only — never accepted on create or update."
          }
        },
        "required": [
          "sensorIri",
          "name",
          "status",
          "createdAt",
          "updatedAt"
        ]
      },
      "EventBatch": {
        "type": "object",
        "properties": {
          "envelopeHash": {
            "type": "string",
            "pattern": "^sha256:[0-9a-f]{64}$",
            "description": "The batch identifier — the tenant-scoped SHA-256 content address of the canonical envelope. Composite-natural PK constraint requires pass-through naming; the design-spec wire alias to 'id' is achieved via x-wire-name on the caliper base."
          },
          "activitySourceId": {
            "type": "string",
            "minLength": 1,
            "description": "The activity source that sent this batch, renamed from sensorIri to plain language. Resolves to the activitySource projection surface for human-readable attribution."
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "description": "The instant the sender says this batch was sent, renamed from sendTime to plain language."
          },
          "receivedAt": {
            "type": "string",
            "format": "date-time",
            "description": "The server-side instant when this tenant received and recorded the batch receipt."
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "processed",
              "rejected",
              "duplicate"
            ],
            "description": "The processing status of the batch: received before normalization completes, processed after events are written, rejected when validation fails, duplicate when the hash matches a prior envelope."
          }
        },
        "required": [
          "envelopeHash",
          "sentAt",
          "receivedAt",
          "status"
        ]
      },
      "SourceImportBatch": {
        "type": "object",
        "properties": {
          "sourceImportId": {
            "type": "string",
            "format": "uuid",
            "description": "Stable identifier for this import batch. Server-generated UUID or client-supplied importId — the idempotency anchor for importSourceEvents."
          },
          "adapterId": {
            "type": "string",
            "enum": [
              "timeback_learning_event_v1",
              "timeback_caliper_raw_v1"
            ],
            "description": "The registered source-adapter shape that processed this batch. Determines which normalizer was dispatched for the adapter registry."
          },
          "status": {
            "type": "string",
            "enum": [
              "received",
              "processed",
              "rejected",
              "partial"
            ],
            "description": "Processing outcome of this import batch. Received = accepted for processing; processed = all rows ingested; rejected = entire batch failed validation; partial = some rows accepted, others quarantined."
          },
          "dryRun": {
            "type": "boolean",
            "description": "Whether this batch was a dry-run-only demonstration. Dry-run batches validate and classify rows but write no caliperEvent records."
          },
          "counts": {
            "type": "object",
            "properties": {
              "accepted": {
                "type": "integer",
                "minimum": 0,
                "description": "Number of rows successfully ingested as caliperEvent records."
              },
              "rejected": {
                "type": "integer",
                "minimum": 0,
                "description": "Number of rows that failed schema or adapter validation."
              },
              "quarantined": {
                "type": "integer",
                "minimum": 0,
                "description": "Number of rows that failed the new schema and were quarantined (never coerced)."
              },
              "resultsRouted": {
                "type": "integer",
                "minimum": 0,
                "description": "Number of rows classified as results-owned and routed toward the results import door."
              }
            },
            "required": [
              "accepted",
              "rejected",
              "quarantined",
              "resultsRouted"
            ],
            "description": "Outcome counts for this import batch: how many rows were accepted, rejected, quarantined, or routed to the results import door.",
            "additionalProperties": false
          },
          "quarantineRefs": {
            "type": "array",
            "items": {
              "type": "object",
              "description": "One quarantined row reference with its violated constraint."
            },
            "description": "References to quarantined rows — rows failing schema validation land here with their violated constraint and reason, never silently coerced.",
            "nullable": true
          },
          "reconciliationUrl": {
            "type": "string",
            "format": "uri",
            "description": "URL for downstream reconciliation of partial or rejected batches. Null when the batch was fully processed.",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "readOnly": true,
            "description": "Server-managed row-creation timestamp (RFC 3339). Read-only — never accepted on create or update."
          }
        },
        "required": [
          "sourceImportId",
          "adapterId",
          "status",
          "dryRun",
          "counts",
          "createdAt"
        ]
      },
      "Problem": {
        "type": "object",
        "description": "RFC 7807 problem details.",
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "A URI identifying the problem type."
          },
          "title": {
            "type": "string",
            "description": "A short, human-readable summary of the problem."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code."
          },
          "detail": {
            "type": "string",
            "description": "A human-readable explanation specific to this occurrence."
          },
          "instance": {
            "type": "string",
            "format": "uri",
            "description": "A URI identifying this occurrence."
          }
        },
        "required": [
          "title",
          "status"
        ]
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      }
    }
  },
  "servers": [
    {
      "url": "https://alpha.platform4.alphaschool.dev"
    }
  ]
}
