{
"$type": "com.atproto.lexicon.schema",
"defs": {
"activity": {
"description": "xAPI object. **Activity만 지원한다**(위 objectType 설명 참조).",
"properties": {
"definition": {
"ref": "#activityDefinition",
"type": "ref"
},
"id": {
"maxLength": 500,
"type": "string"
},
"objectType": {
"description": "이 def는 Activity만 표현한다. xAPI가 허용하는 Agent/Group/StatementRef/SubStatement는 필드 구조가 달라(Agent는 id 대신 IFI, SubStatement는 actor/verb/object를 갖고 id를 가질 수 없다) 이 def로 담을 수 없다. **지원하지 않는다**고 명시한다 — 이전 스키마는 enum이 4종을 광고하면서 id를 required로 두어 그 statement를 전부 거부했다(2026-09-09 검토 지적). 필요해지면 union + 별도 def로 분해하고 ADR-0007에 기록한다.",
"enum": [
"Activity"
],
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"activityDefinition": {
"properties": {
"correctResponsesPattern": {
"items": {
"maxLength": 1000,
"type": "string"
},
"maxLength": 64,
"type": "array"
},
"description": {
"description": "language map — unknown",
"type": "unknown"
},
"extensions": {
"description": "IRI-keyed 열린 맵 — unknown",
"type": "unknown"
},
"extensionsJson": {
"description": "소수를 포함한 extensions 보존용(ADR: atproto 데이터 모델은 float 금지). extensions와 배타적.",
"maxLength": 20000,
"type": "string"
},
"interactionType": {
"enum": [
"true-false",
"choice",
"fill-in",
"long-fill-in",
"matching",
"performance",
"sequencing",
"likert",
"numeric",
"other"
],
"type": "string"
},
"moreInfo": {
"format": "uri",
"type": "string"
},
"name": {
"description": "language map — unknown",
"type": "unknown"
},
"type": {
"format": "uri",
"type": "string"
}
},
"type": "object"
},
"agent": {
"description": "xAPI Agent/Group. **required를 두지 않는다** — xAPI 1.0.3은 objectType을 optional로 두고 IFI(mbox / mbox_sha1sum / openid / account) **정확히 하나**를 요구한다. Lexicon은 \"넷 중 정확히 하나\"를 표현할 수 없으므로 그 규칙은 변환기·검증기의 추가 규칙이다(2026-09-09 검토 지적: 이전 스키마는 objectType을 required로 만들어 유효한 xAPI actor를 거부하고, 식별자 없는 actor를 통과시켰다).",
"properties": {
"account": {
"ref": "#agentAccount",
"type": "ref"
},
"mbox": {
"maxLength": 300,
"type": "string"
},
"mbox_sha1sum": {
"maxLength": 64,
"type": "string"
},
"name": {
"maxLength": 300,
"type": "string"
},
"objectType": {
"enum": [
"Agent",
"Group"
],
"type": "string"
},
"openid": {
"format": "uri",
"type": "string"
}
},
"type": "object"
},
"agentAccount": {
"properties": {
"homePage": {
"format": "uri",
"type": "string"
},
"name": {
"maxLength": 300,
"type": "string"
}
},
"required": [
"homePage",
"name"
],
"type": "object"
},
"attachment": {
"properties": {
"contentType": {
"maxLength": 128,
"type": "string"
},
"description": {
"type": "unknown"
},
"display": {
"description": "language map — unknown",
"type": "unknown"
},
"extensions": {
"description": "IRI-keyed 열린 맵 — unknown",
"type": "unknown"
},
"extensionsJson": {
"description": "동일. 2026-09-09 검토에서 이 경로에 필드가 없어 소수 인코딩이 불가능하다는 지적을 받아 추가.",
"maxLength": 20000,
"type": "string"
},
"fileUrl": {
"format": "uri",
"type": "string"
},
"length": {
"minimum": 0,
"type": "integer"
},
"sha2": {
"maxLength": 128,
"type": "string"
},
"usageType": {
"format": "uri",
"type": "string"
}
},
"required": [
"usageType",
"display",
"contentType",
"length",
"sha2"
],
"type": "object"
},
"context": {
"properties": {
"contextActivities": {
"ref": "#contextActivities",
"type": "ref"
},
"extensions": {
"type": "unknown"
},
"extensionsJson": {
"description": "소수를 포함한 extensions는 atproto 데이터 모델(float 금지)에 담을 수 없다. JSON 문자열로 보존한다 — 무손실이지만 **검증·질의 불가**다. extensions와 배타적으로 쓴다.",
"maxLength": 20000,
"type": "string"
},
"instructor": {
"ref": "#agent",
"type": "ref"
},
"language": {
"maxLength": 32,
"type": "string"
},
"platform": {
"maxLength": 300,
"type": "string"
},
"registration": {
"maxLength": 64,
"type": "string"
},
"revision": {
"maxLength": 300,
"type": "string"
},
"statement": {
"ref": "#statementRef",
"type": "ref"
},
"team": {
"ref": "#agent",
"type": "ref"
}
},
"type": "object"
},
"contextActivities": {
"properties": {
"category": {
"items": {
"ref": "#activity",
"type": "ref"
},
"maxLength": 32,
"type": "array"
},
"grouping": {
"items": {
"ref": "#activity",
"type": "ref"
},
"maxLength": 32,
"type": "array"
},
"other": {
"items": {
"ref": "#activity",
"type": "ref"
},
"maxLength": 32,
"type": "array"
},
"parent": {
"items": {
"ref": "#activity",
"type": "ref"
},
"maxLength": 32,
"type": "array"
}
},
"type": "object"
},
"main": {
"description": "학습 이벤트. xAPI statement를 임베드한다(제약 3번: 의미론은 표준 차용, 봉투만 자체 정의). ADR-0001: 비공개 기본이며 AppView 인덱싱 대상이 아니다.",
"key": "tid",
"record": {
"properties": {
"course": {
"maxLength": 300,
"type": "string"
},
"node": {
"maxLength": 300,
"type": "string"
},
"recordedAt": {
"description": "봉투 필드. xAPI의 stored와 구분한다.",
"format": "datetime",
"type": "string"
},
"statement": {
"ref": "#xapiStatement",
"type": "ref"
}
},
"required": [
"statement",
"recordedAt"
],
"type": "object"
},
"type": "record"
},
"result": {
"description": "xAPI result. score는 소수이므로 **정수 스케일로 인코딩**한다(Lexicon에 float 없음). 원본 복원을 위해 스케일을 함께 기록한다.",
"properties": {
"completion": {
"type": "boolean"
},
"duration": {
"description": "ISO 8601 duration. 문자열이므로 무손실이다.",
"maxLength": 64,
"type": "string"
},
"extensions": {
"type": "unknown"
},
"extensionsJson": {
"description": "소수를 포함한 extensions는 atproto 데이터 모델(float 금지)에 담을 수 없다. JSON 문자열로 보존한다 — 무손실이지만 **검증·질의 불가**다. extensions와 배타적으로 쓴다.",
"maxLength": 20000,
"type": "string"
},
"response": {
"maxLength": 10000,
"type": "string"
},
"score": {
"ref": "#score",
"type": "ref"
},
"success": {
"type": "boolean"
}
},
"type": "object"
},
"score": {
"description": "xAPI score. **정수 인코딩만으로는 무손실이 아니다**(S7 실측: 1/3 → 0.333, 0.8765 → 0.877). 원본 10진 문자열을 함께 보존해 왕복을 무손실로 만들고, 정수 필드는 질의·정렬용으로 둔다.",
"properties": {
"maxScaled": {
"description": " 범위는 IEEE754 안전 정수(±2^53-1). 초과는 변환기가 실패로 보고한다.",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"maxStr": {
"maxLength": 32,
"type": "string"
},
"minScaled": {
"description": " 범위는 IEEE754 안전 정수(±2^53-1). 초과는 변환기가 실패로 보고한다.",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"minStr": {
"maxLength": 32,
"type": "string"
},
"rawScaleExp": {
"default": 0,
"description": "raw/min/max에 적용한 10의 지수. 변환기는 고정값 6만 쓴다(`roundtrip.mjs` RAW_EXP). 스키마는 0~9를 허용하나 지수를 레코드마다 바꾸면 정수 필드 간 비교·정렬이 깨진다. 왕복 복원은 문자열 필드가 정본이다.",
"maximum": 9,
"minimum": 0,
"type": "integer"
},
"rawScaled": {
"description": "xAPI raw × 10^rawScaleExp. 범위는 IEEE754 안전 정수(±2^53-1). 초과는 변환기가 실패로 보고한다.",
"maximum": 9007199254740991,
"minimum": -9007199254740991,
"type": "integer"
},
"rawStr": {
"maxLength": 32,
"type": "string"
},
"scaledMilli": {
"description": "xAPI scaled × 1000.",
"maximum": 1000,
"minimum": -1000,
"type": "integer"
},
"scaledStr": {
"description": "원본 10진 문자열. 왕복 복원의 근거다. 정수 필드보다 우선한다.",
"maxLength": 32,
"type": "string"
}
},
"type": "object"
},
"statementRef": {
"properties": {
"id": {
"maxLength": 64,
"type": "string"
},
"objectType": {
"enum": [
"StatementRef"
],
"type": "string"
}
},
"required": [
"objectType",
"id"
],
"type": "object"
},
"verb": {
"properties": {
"display": {
"description": "xAPI language map. **Lexicon에 열린 맵 타입이 없어 unknown으로 담는다** — 검증이 불가능해진다(S7 발견 2). 키는 RFC 5646 언어 태그.",
"type": "unknown"
},
"id": {
"format": "uri",
"type": "string"
}
},
"required": [
"id"
],
"type": "object"
},
"xapiStatement": {
"description": "xAPI 1.0.3 statement. 필드 이름·구조를 표준 그대로 유지한다.",
"properties": {
"actor": {
"ref": "#agent",
"type": "ref"
},
"attachments": {
"items": {
"ref": "#attachment",
"type": "ref"
},
"maxLength": 32,
"type": "array"
},
"context": {
"ref": "#context",
"type": "ref"
},
"id": {
"description": "UUID.",
"maxLength": 64,
"type": "string"
},
"object": {
"ref": "#activity",
"type": "ref"
},
"result": {
"ref": "#result",
"type": "ref"
},
"stored": {
"format": "datetime",
"type": "string"
},
"timestamp": {
"format": "datetime",
"type": "string"
},
"verb": {
"ref": "#verb",
"type": "ref"
},
"version": {
"maxLength": 16,
"type": "string"
}
},
"required": [
"actor",
"verb",
"object"
],
"type": "object"
}
},
"id": "uk.gdeo.learn.event",
"lexicon": 1
}