uk.gdeo.learn.app

mlldaniel.bsky.social

{
  "$type": "com.atproto.lexicon.schema",
  "defs": {
    "assessConfig": {
      "description": "과제와 채점 **방식**이 필수다. `runners`(실행기)는 선택 — 루브릭 전용 평가는 실행기가 필요 없다. \"graders에 verifier가 있으면 runners가 필수\"는 조건부 제약이라 Lexicon으로 표현할 수 없다(oneOf·if/then 없음) — 저작 파이프라인이 검사한다.",
      "properties": {
        "assignments": {
          "maxLength": 300,
          "type": "string"
        },
        "graders": {
          "description": "채점 **방식**만. 실행기는 runner로 분리했다(ADR-0014) — 종전 enum이 방식과 실행기를 섞어 방식 없는 조합도 통과시켰다.",
          "items": {
            "enum": [
              "verifier",
              "rubric"
            ],
            "type": "string"
          },
          "maxLength": 4,
          "type": "array"
        },
        "runners": {
          "description": "채점 실행기의 레지스트리 키(NSID). 형식만 검사하고 유효성은 레지스트리가 판정한다(ADR-0014·0015).",
          "items": {
            "format": "nsid",
            "maxLength": 317,
            "type": "string"
          },
          "maxLength": 8,
          "type": "array"
        }
      },
      "required": [
        "assignments",
        "graders"
      ],
      "type": "object"
    },
    "assessWidget": {
      "description": "평가 위젯. **소스 근거 없는 순수 제안이다**(S6 RESULT, Q33).",
      "properties": {
        "config": {
          "ref": "#assessConfig",
          "type": "ref"
        },
        "type": {
          "const": "assess",
          "description": "union의 `$type`과 중복이지만 const로 고정돼 불일치가 불가능하다. `flow.entry`·`then`의 enum이 이 값을 가리킨다.",
          "type": "string"
        }
      },
      "required": [
        "type",
        "config"
      ],
      "type": "object"
    },
    "attribution": {
      "description": "파일 헤더에서 기계 수집. 손으로 옮기면 과소 귀속이 된다(matrix-spec 실측: 주체 11~12개).",
      "properties": {
        "coverage": {
          "ref": "#attributionCoverage",
          "type": "ref"
        },
        "holders": {
          "items": {
            "ref": "#attributionHolder",
            "type": "ref"
          },
          "maxLength": 500,
          "type": "array"
        }
      },
      "required": [
        "coverage",
        "holders"
      ],
      "type": "object"
    },
    "attributionCoverage": {
      "properties": {
        "filesIngested": {
          "minimum": 0,
          "type": "integer"
        },
        "filesWithNotice": {
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "filesIngested",
        "filesWithNotice"
      ],
      "type": "object"
    },
    "attributionHolder": {
      "properties": {
        "files": {
          "minimum": 0,
          "type": "integer"
        },
        "name": {
          "maxLength": 200,
          "type": "string"
        },
        "years": {
          "maxLength": 32,
          "type": "string"
        }
      },
      "required": [
        "name"
      ],
      "type": "object"
    },
    "corpus": {
      "description": "인제스트 대상. ADR-0011: 렌더 URL이 아니라 리비전으로 고정한다(Q39).",
      "properties": {
        "attribution": {
          "ref": "#attribution",
          "type": "ref"
        },
        "commit": {
          "description": "재현성의 근거. 렌더 사이트 URL은 빌드 커밋 정보가 없어 재빌드 불가.",
          "maxLength": 64,
          "minLength": 7,
          "type": "string"
        },
        "exclude": {
          "items": {
            "ref": "#excludeRule",
            "type": "ref"
          },
          "maxLength": 200,
          "type": "array"
        },
        "include": {
          "items": {
            "ref": "#includeRule",
            "type": "ref"
          },
          "maxLength": 200,
          "type": "array"
        },
        "license": {
          "ref": "#licenseRecord",
          "type": "ref"
        },
        "notice": {
          "ref": "#noticeRecord",
          "type": "ref"
        },
        "packs": {
          "items": {
            "ref": "#packRef",
            "type": "ref"
          },
          "maxLength": 64,
          "type": "array"
        },
        "repo": {
          "format": "uri",
          "type": "string"
        },
        "rights": {
          "ref": "#rightsAxes",
          "type": "ref"
        },
        "upstream": {
          "description": "포크 원본.",
          "format": "uri",
          "type": "string"
        }
      },
      "required": [
        "repo",
        "commit",
        "rights",
        "license",
        "notice",
        "attribution"
      ],
      "type": "object"
    },
    "countComparator": {
      "description": "개수·일수 지표 전용. 값에 스케일이 없다 — `value: 30`은 30회 또는 30일이다.",
      "properties": {
        "metric": {
          "enum": [
            "attempts",
            "elapsedDays"
          ],
          "type": "string"
        },
        "op": {
          "enum": [
            "lt",
            "lte",
            "gt",
            "gte",
            "eq",
            "ne"
          ],
          "type": "string"
        },
        "then": {
          "enum": [
            "reference",
            "explorer",
            "drill",
            "sandbox",
            "assess",
            "masteryMap"
          ],
          "type": "string"
        },
        "value": {
          "description": "스케일 없는 정수. 상한 36500은 도메인 측정이 아니라 **오타·폭주 방지용 임의 상한**이다(elapsedDays 100년치). attempts도 같은 상한을 공유한다.",
          "maximum": 36500,
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "metric",
        "op",
        "value",
        "then"
      ],
      "type": "object"
    },
    "drillConfig": {
      "description": "카드셋과 스케줄러가 없으면 복습이 성립하지 않는다.",
      "properties": {
        "cardSet": {
          "maxLength": 300,
          "type": "string"
        },
        "scheduler": {
          "description": "제약 6번: 자체 스케줄러 금지.",
          "enum": [
            "fsrs"
          ],
          "type": "string"
        }
      },
      "required": [
        "cardSet",
        "scheduler"
      ],
      "type": "object"
    },
    "drillWidget": {
      "description": "복습 위젯.",
      "properties": {
        "config": {
          "ref": "#drillConfig",
          "type": "ref"
        },
        "type": {
          "const": "drill",
          "description": "union의 `$type`과 중복이지만 const로 고정돼 불일치가 불가능하다. `flow.entry`·`then`의 enum이 이 값을 가리킨다.",
          "type": "string"
        }
      },
      "required": [
        "type",
        "config"
      ],
      "type": "object"
    },
    "excludeRule": {
      "description": "제외 사유를 닫힌 enum으로 강제한다 — 자유 문자열은 검사할 수 없다(ADR-0005).",
      "properties": {
        "path": {
          "maxLength": 500,
          "type": "string"
        },
        "reason": {
          "enum": [
            "unconfirmed_content",
            "superseded",
            "not_learning_material",
            "build_artifact",
            "rights_denied",
            "duplicate"
          ],
          "type": "string"
        }
      },
      "required": [
        "path",
        "reason"
      ],
      "type": "object"
    },
    "explorerWidget": {
      "description": "탐색 위젯. config 형태는 reference와 같아 같은 def을 가리킨다 — 지금 같다는 사실을 숨기지 않는다.",
      "properties": {
        "config": {
          "ref": "#indexConfig",
          "type": "ref"
        },
        "type": {
          "const": "explorer",
          "description": "union의 `$type`과 중복이지만 const로 고정돼 불일치가 불가능하다. `flow.entry`·`then`의 enum이 이 값을 가리킨다.",
          "type": "string"
        }
      },
      "required": [
        "type",
        "config"
      ],
      "type": "object"
    },
    "flow": {
      "properties": {
        "entry": {
          "enum": [
            "reference",
            "explorer",
            "drill",
            "sandbox",
            "assess",
            "masteryMap"
          ],
          "type": "string"
        },
        "rules": {
          "items": {
            "closed": true,
            "description": "ADR-0015: 천분율 지표와 개수·일수 지표는 값의 스케일이 달라 한 def에 담을 수 없다. 종전 단일 `#comparator`는 `elapsedDays > 30`(30일)을 0.03으로 읽었고 `> 2000`(2000일)은 maximum 1000에 걸려 표현 자체가 불가능했다.",
            "refs": [
              "#ratioComparator",
              "#countComparator"
            ],
            "type": "union"
          },
          "maxLength": 32,
          "type": "array"
        }
      },
      "required": [
        "entry",
        "rules"
      ],
      "type": "object"
    },
    "includeRule": {
      "properties": {
        "path": {
          "maxLength": 500,
          "type": "string"
        }
      },
      "required": [
        "path"
      ],
      "type": "object"
    },
    "indexConfig": {
      "description": "reference·explorer 공용. 색인 없이는 두 위젯 모두 렌더할 대상이 없으므로 `index`가 필수다.",
      "properties": {
        "groupBy": {
          "enum": [
            "apiSurface",
            "module",
            "chapter",
            "theorem",
            "text",
            "version"
          ],
          "type": "string"
        },
        "index": {
          "enum": [
            "markdown",
            "openapi",
            "eventSchema",
            "theoremGraph",
            "annotatedText"
          ],
          "type": "string"
        },
        "secondaryIndex": {
          "enum": [
            "markdown",
            "openapi",
            "eventSchema",
            "theoremGraph",
            "annotatedText"
          ],
          "type": "string"
        }
      },
      "required": [
        "index"
      ],
      "type": "object"
    },
    "licenseRecord": {
      "description": "ADR-0011 6항: 계산값. 저자가 손으로 적지 않는다.",
      "properties": {
        "perFileChecked": {
          "description": "리포 단위 LICENSE 선언만 믿지 않고 파일별로 검사했는가.",
          "type": "boolean"
        },
        "spdx": {
          "maxLength": 64,
          "type": "string"
        },
        "textRef": {
          "accept": [
            "text/plain"
          ],
          "description": "Apache-2.0 §4(a): 라이선스 전문 사본. 링크가 아니라 동봉이다. [미구현] 스키마는 조건부 required를 표현할 수 없다. 강제는 빌드 게이트(미작성) 몫이며 현재 강제되지 않는다.",
          "maxSize": 1000000,
          "type": "blob"
        }
      },
      "required": [
        "spdx",
        "perFileChecked"
      ],
      "type": "object"
    },
    "main": {
      "description": "도메인 학습 앱의 선언적 정의. 코드가 아니라 데이터다. ADR-0005: 필드는 닫힌 enum·비교기·레지스트리 ID만 허용한다.",
      "key": "tid",
      "record": {
        "properties": {
          "corpus": {
            "ref": "#corpus",
            "type": "ref"
          },
          "flow": {
            "ref": "#flow",
            "type": "ref"
          },
          "forkOf": {
            "description": "포크 계보. 자기 신고 필드이므로 위조 가능하다.",
            "format": "at-uri",
            "type": "string"
          },
          "license": {
            "description": "앱 정의 자체의 라이선스(SPDX).",
            "maxLength": 64,
            "type": "string"
          },
          "name": {
            "description": "표시 이름. 제3자 상표를 포함하면 trademarkClearance 없이 publish 거부(ADR-0011 7항).",
            "maxGraphemes": 100,
            "maxLength": 300,
            "type": "string"
          },
          "offer": {
            "format": "at-uri",
            "type": "string"
          },
          "pattern": {
            "description": "소스 유형 기반 템플릿. enum이므로 새 패턴은 Lexicon 개정을 요구한다.",
            "enum": [
              "spec-corpus",
              "book-corpus",
              "problem-set",
              "text-corpus"
            ],
            "type": "string"
          },
          "runtime": {
            "description": "렌더러 최소 버전 요구.",
            "maxLength": 32,
            "type": "string"
          },
          "trademarkClearance": {
            "description": "제3자 상표 사용의 사전 서면 동의 근거. 없으면 상표 포함 명명 불가.",
            "ref": "#trademarkClearance",
            "type": "ref"
          },
          "version": {
            "maxLength": 32,
            "type": "string"
          },
          "widgets": {
            "items": {
              "closed": true,
              "description": "ADR-0015: 위젯 타입마다 별도 def이고 config의 필수 필드가 다르므로 닫힌 union이다. 데이터에는 `$type`이 필수이며 형식은 전체 NSID(`uk.gdeo.learn.app#drillWidget`)다 — atproto Lexicon 스펙 \"union variants must always include $type\". 종전 plain ref는 config에 required가 하나도 없어 `config: {}`를 통과시켰다(반례 확인 2026-09-10).",
              "refs": [
                "#referenceWidget",
                "#explorerWidget",
                "#drillWidget",
                "#sandboxWidget",
                "#assessWidget",
                "#masteryMapWidget"
              ],
              "type": "union"
            },
            "maxLength": 16,
            "minLength": 1,
            "type": "array"
          }
        },
        "required": [
          "name",
          "version",
          "runtime",
          "pattern",
          "license",
          "corpus",
          "widgets",
          "flow"
        ],
        "type": "object"
      },
      "type": "record"
    },
    "masteryMapConfig": {
      "description": "스킬 그래프 참조가 없으면 그릴 것이 없다.",
      "properties": {
        "graph": {
          "maxLength": 300,
          "type": "string"
        }
      },
      "required": [
        "graph"
      ],
      "type": "object"
    },
    "masteryMapWidget": {
      "description": "숙달도 맵 위젯.",
      "properties": {
        "config": {
          "ref": "#masteryMapConfig",
          "type": "ref"
        },
        "type": {
          "const": "masteryMap",
          "description": "union의 `$type`과 중복이지만 const로 고정돼 불일치가 불가능하다. `flow.entry`·`then`의 enum이 이 값을 가리킨다.",
          "type": "string"
        }
      },
      "required": [
        "type",
        "config"
      ],
      "type": "object"
    },
    "noticeRecord": {
      "description": "빌드 타임 검사 결과. present:true인데 textRef가 없으면 빌드 실패. [미구현] 이 조건은 Lexicon required로 표현되지 않으며 빌드 게이트가 아직 없다.",
      "properties": {
        "present": {
          "type": "boolean"
        },
        "textRef": {
          "accept": [
            "text/plain"
          ],
          "maxSize": 1000000,
          "type": "blob"
        }
      },
      "required": [
        "present"
      ],
      "type": "object"
    },
    "packRef": {
      "properties": {
        "ref": {
          "format": "at-uri",
          "type": "string"
        },
        "visibility": {
          "description": "ADR-0001: public만 AppView 인덱싱 대상. gated는 미리보기 메타만.",
          "enum": [
            "public",
            "gated",
            "private"
          ],
          "type": "string"
        }
      },
      "required": [
        "ref",
        "visibility"
      ],
      "type": "object"
    },
    "ratioComparator": {
      "description": "ADR-0005: 평가기가 아니라 비교기다. 표현식 문자열을 두지 않는다. 이 def은 **비율 지표 전용**이다.",
      "properties": {
        "metric": {
          "enum": [
            "mastery",
            "retrievability"
          ],
          "type": "string"
        },
        "op": {
          "enum": [
            "lt",
            "lte",
            "gt",
            "gte",
            "eq",
            "ne"
          ],
          "type": "string"
        },
        "then": {
          "enum": [
            "reference",
            "explorer",
            "drill",
            "sandbox",
            "assess",
            "masteryMap"
          ],
          "type": "string"
        },
        "valueMilli": {
          "description": "천분율(0~1000). Lexicon에 float가 없으므로 정수로 표현한다 — 0.6은 600이다. 필드명에 스케일을 박아 개수·일수와 섞이지 않게 한다.",
          "maximum": 1000,
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "metric",
        "op",
        "valueMilli",
        "then"
      ],
      "type": "object"
    },
    "referenceWidget": {
      "description": "참조 위젯. 코퍼스를 색인으로 훑는다.",
      "properties": {
        "config": {
          "ref": "#indexConfig",
          "type": "ref"
        },
        "type": {
          "const": "reference",
          "description": "union의 `$type`과 중복이지만 const로 고정돼 불일치가 불가능하다. `flow.entry`·`then`의 enum이 이 값을 가리킨다.",
          "type": "string"
        }
      },
      "required": [
        "type",
        "config"
      ],
      "type": "object"
    },
    "rightsAxes": {
      "description": "ADR-0011: 권리는 단일 라이선스 문자열이 아니라 축이다. 판정 단위는 문서다.",
      "properties": {
        "commercial": {
          "enum": [
            "allow",
            "deny"
          ],
          "type": "string"
        },
        "copyleft": {
          "enum": [
            "none",
            "share_alike"
          ],
          "type": "string"
        },
        "derivative": {
          "enum": [
            "allow",
            "deny"
          ],
          "type": "string"
        },
        "excerpt_limit": {
          "default": "none",
          "enum": [
            "none",
            "ietf-one-fifth"
          ],
          "type": "string"
        },
        "license_id": {
          "description": "unknown의 기본 동작은 link_only다(ADR-0011 3항).",
          "enum": [
            "CC0-1.0",
            "CC-BY-4.0",
            "CC-BY-SA-4.0",
            "Apache-2.0",
            "MIT",
            "W3C-Software-and-Document",
            "W3C-Document",
            "IETF-TLP",
            "arXiv-nonexclusive-1.0",
            "CC-BY-NC-SA-4.0",
            "CC-BY-NC-ND-4.0",
            "public-domain",
            "unknown"
          ],
          "type": "string"
        },
        "notice_obligations": {
          "items": {
            "enum": [
              "attribution",
              "license_text",
              "change_indication",
              "ietf_legends",
              "w3c_status"
            ],
            "type": "string"
          },
          "maxLength": 8,
          "type": "array"
        },
        "verbatim_excerpt": {
          "enum": [
            "allow",
            "deny"
          ],
          "type": "string"
        }
      },
      "required": [
        "license_id",
        "verbatim_excerpt",
        "derivative",
        "commercial",
        "copyleft"
      ],
      "type": "object"
    },
    "sandboxConfig": {
      "description": "실행 환경을 지목하는 레지스트리 키가 없으면 샌드박스가 없다.",
      "properties": {
        "provider": {
          "description": "플랫폼 소유 provider 레지스트리(`uk.gdeo.learn.providerRegistry`)의 키. **enum이 아니다**(ADR-0014) — 종전 enum 3개가 첫 코스의 샌드박스 3개와 1:1 대응해 확정 제약 8번을 어겼다. **키는 NSID다**(ADR-0015) — 종전 `format: nsid-like`는 스펙 공식 목록 11종에 없는 발명값이었고 공식 검증기 동작이 미확인이었다. 제3자는 자기 도메인 권한으로 등록한다(`com.example.provider.foo`). 구문은 스키마가, 존재 여부는 레지스트리가 판정한다. maxLength 317은 NSID 전체 길이 상한(스펙, 확인일 2026-09-10). 컨테이너 이미지를 지목하지 않는다(ADR-0005).",
          "format": "nsid",
          "maxLength": 317,
          "type": "string"
        }
      },
      "required": [
        "provider"
      ],
      "type": "object"
    },
    "sandboxWidget": {
      "description": "샌드박스 위젯. **소스 근거 없는 순수 제안이다**(S6 RESULT, Q33).",
      "properties": {
        "config": {
          "ref": "#sandboxConfig",
          "type": "ref"
        },
        "type": {
          "const": "sandbox",
          "description": "union의 `$type`과 중복이지만 const로 고정돼 불일치가 불가능하다. `flow.entry`·`then`의 enum이 이 값을 가리킨다.",
          "type": "string"
        }
      },
      "required": [
        "type",
        "config"
      ],
      "type": "object"
    },
    "trademarkClearance": {
      "properties": {
        "evidenceRef": {
          "description": "사전 서면 동의의 근거 참조.",
          "maxLength": 500,
          "type": "string"
        },
        "grantedAt": {
          "format": "datetime",
          "type": "string"
        },
        "grantedBy": {
          "maxLength": 200,
          "type": "string"
        },
        "mark": {
          "maxLength": 100,
          "type": "string"
        }
      },
      "required": [
        "mark",
        "grantedBy",
        "evidenceRef"
      ],
      "type": "object"
    }
  },
  "id": "uk.gdeo.learn.app",
  "lexicon": 1
}

Validate Record

Validate a record against uk.gdeo.learn.app

Validation Options
Treat any remaining unresolved references as valid

Metadata

DID
did:plc:nyobcyqr4crkx7nisq5ts7dt
CID
bafyreidy4nzoosrli5wzumwwqbbd3fepafot3mlhf5of65jnjdorzaw5pi
Indexed At
2026-09-11 07:34 UTC
AT-URI
at://did:plc:nyobcyqr4crkx7nisq5ts7dt/com.atproto.lexicon.schema/uk.gdeo.learn.app

Lexicon Garden

@