| id | learning-yaml-contract |
|---|---|
| title | Learning YAML Contract |
| description | Structured YAML contract for Codaro curriculum lessons and section-card rendering. |
| category | architecture |
| section | curriculum |
| order | 207 |
| purpose | 학습 YAML을 단순 렌더링 데이터가 아니라 레슨 설계 SSOT로 고정한다. |
| whenToUse | 커리큘럼 YAML 생성, write-curriculum-yaml materializer, 섹션 학습카드 UI, teacher eval case를 바꿀 때. |
Codaro의 학습 YAML은 화면 조각 목록이 아니라 학습 설계의 source of truth다. YAML은 lesson SSOT다. 프론트는 YAML 의미를 추측하지 않고 contract payload를 읽어 렌더링한다.
신규 레슨과 teacher가 생성하는 YAML은 아래 구조를 우선한다.
meta:
title: 3단계 pandas 실습 레슨
audience: 초급
difficulty: easy
packages:
- pandas
intro:
direction: DataFrame 생성, 확인, 수정 흐름을 한 번에 익힌다.
benefits:
- 표 데이터를 코드로 만들고 검증할 수 있다.
diagram:
steps:
- label: DataFrame 입력 확인
detail: sales 열과 행 값을 먼저 고정한다.
- label: DataFrame 처리 실행
detail: pandas 생성 코드를 실행해 중간 결과를 확인한다.
- label: sales 결과 검증
detail: 행/열 수와 요약값 기준으로 실행 결과를 비교한다.
- label: DataFrame 재사용
detail: 검증된 코드를 작은 리포트 자동화에 붙일 수 있게 정리한다.
runtime:
- label: pandas 환경
detail: pandas 기준으로 로컬 Python 실행을 준비한다.
- label: 검증
detail: 실행 결과
sections:
- id: dataframe-basics
title: DataFrame 만들기
structuredPrimary: true
subtitle: 행과 열의 감각
goal: dict에서 DataFrame을 만드는 흐름을 익힌다.
why: 엑셀 표 자동화의 첫 단계다.
explanation: pandas.DataFrame은 열 이름과 값 목록으로 표를 만든다.
tips:
- 모든 열의 길이는 같아야 한다.
snippet: |
import pandas as pd
frame = pd.DataFrame({"name": ["A"], "sales": [10]})
frame
exercise:
prompt: sales 열을 가진 DataFrame을 직접 만드세요.
starterCode: |
import pandas as pd
frame = ___
solution: |
import pandas as pd
frame = pd.DataFrame({"sales": [10, 20]})
hints:
- dict의 key가 열 이름이다.
check:
type: noError
noError: DataFrame 생성 코드가 pandas import, 열 이름, 행 길이 조건을 만족해야 한다.
resultCheck: frame 변수에 sales 열이 있고, 바꾼 행 값이 DataFrame 결과에 반영되어야 한다.위 noError와 서술형 resultCheck는 weak feedback 계약이다. 실행 성공만으로 lesson completion, mastery, retrieval credit을 만들지 않는다. 현재 구현된 첫 Web strong slice는 다음처럼 versioned output spec을 사용한다.
check:
id: python.print.hello-codaro.output.v1
version: 1
kind: output
strength: strong
executor: browser-worker
timeoutMs: 8000
fixtureId: python.print.hello-codaro.fixture.v1
fixtureHash: sha256-EUE3dsIaRrkQcqkx52hMvHYX4XSUaDqh+aRH0f9shqI=
fixture:
directories: []
env:
LANG: C.UTF-8
TZ: UTC
files: []
stdin: []
payload:
comparator: exact
expected: Hello Codaro
normalization: trim-final-newlinefixture hash는 key를 정렬한 compact JSON UTF-8 bytes의 SHA-256 SRI다. author gate가 hash를 다시 계산하므로 임의 문자열을 넣어 통과시킬 수 없다. contracts/checkSandboxFeasibilityDecision.json이 Web과 Local의 실행 가능 evidence를 함께 결정한다. Web output·직렬화 variable만 fresh pyproc Worker와 processWorker graph SRI, fixture hash, timeout, teardown을 통과하면 strong 후보가 되며 behavior는 Worker boot 전에 localRequired로 끝난다. Local local-sandbox는 같은 behavior spec을 별도 native Python 자식 프로세스에서 실행한다. Windows launcher 경로는 managed runtime·worker를 tree hash와 active release로 고정하고 AppContainer capability 0, Job Object, handle allowlist, HMAC named pipe, 실행별 ACL receipt v2로 fixture 밖 읽기, network와 child process를 OS 경계에서도 거부한다. 공유 DACL mutex는 동시 grant·revoke를 직렬화하며 회수 실패 receipt/profile은 다음 launcher 시작이 stale run으로 GC한다. Local package asset은 설치본 CODARO_WEB_BUILD_ROOT의 pinned wheel만 사용하고 cold 병렬 검사도 캐시를 만들지 않는다. 다만 목표 Windows 10 22H2 설치본 conformance가 없으므로 결과를 practice 피드백으로만 제공하고 strong evidence를 append하지 않는다. behavior 판정은 file·directory·table·image descriptor와 package SRI를 계속 계산하지만 release conformance 전에는 공용 evidence payload로 승격하지 않는다. 임의 원격 URL이나 최신 버전 즉석 설치는 strong evidence에 사용할 수 없다.
강한 pass는 raw source/output이 아니라 source/result/expected hash, check/fixture ID, canonical lessonRef와 실제 runtime tier를 append-only event로 저장한다. Web은 runtimeTier: web과 web-strong:<fingerprint>, Local native 검사는 runtimeTier: local과 local-strong:<fingerprint>를 사용하며 fingerprint에도 tier를 넣어 같은 source의 서로 다른 실행을 충돌로 오해하지 않는다. archive manifest는 event 집합에 따라 web, local, mixed를 기록한다. JSON archive의 canonical event bytes, event-set hash, 개별 payload hash가 모두 맞아야 import할 수 있다. Web은 IndexedDB v3의 metadata header와 evidence store를, Local은 별도 SQLite transaction과 sidecar header를 사용한다. 두 tier 모두 schema/data epoch와 minimum reader floor를 검사한 뒤 eventId set union을 수행하고 동일 ID의 다른 payload는 원본을 덮어쓰지 않고 conflicts store에 격리한다. 이전 web-evidence: archive ID와 category-scoped legacy lesson alias는 검증 뒤 현재 identity로 이관한다. 이 event archive import는 progress.json, lesson completion, outcome credit을 수정하지 않는다. Local behavior artifact descriptor와 pinned package asset descriptor는 봉인됐지만 notebook/document, draft, 전체 virtual FS artifact와 package set archive가 포함되기 전에는 전체 Web-to-Local 학습 archive로 부르지 않는다.
assessment.masteryVariants, assessment.transferVariants, assessment.retrievalVariants는 base section 정답을 복사한 추가 문제가 아니다. 각 variant는 mode, 실제 sourceSectionIds, 독립 starter/solution, strong CheckSpec을 가져야 하며 snippet을 포함하지 않는다. mastery는 unseen: false, transfer와 retrieval은 unseen: true, retrieval은 minimumDelayHours >= 24를 추가로 요구한다.
- mastery만 base lesson 마지막에
혼자 완성하기로 materialize한다. - transfer는 base lesson에 즉시 materialize하지 않는다. source mastery strong event가 저장되면
새 조건에 적용으로 자동 제공한다. - retrieval도 base lesson에 즉시 materialize하지 않는다. Web·Local 표면 queue가 같은
lessonRef의 source strong event 시각을 읽고 delay가 지난 경우에만기억에서 다시 풀기로 자동 제공한다. - due 여부를 확인하거나 문제를 펼치는 별도 버튼을 만들지 않는다. route 진입과 evidence 갱신 시 queue가 자동 재계산된다.
- transfer 또는 retrieval strong event가 저장되면 같은 check ID의 due 카드는 queue에서 빠진다.
- variant 배열이 존재하거나 ID가 생성됐다는 사실은 학습 evidence가 아니다. strong executor 실제 통과와 append-only event가 있어야 실행 증거로 계산한다.
- 현재 machine audit의 source 저작 범위는 strong CheckSpec 1,419개/468레슨이며 mastery·transfer·24시간 retrieval은 각각 468레슨이다. 1,402개 assessment solution은 1,400개 behavior와 2개 output 검증으로 실행됐고 실패는 0이다. 이것은 author source 검산이며 제품 strong evidence 지원 범위와 다르다. Web behavior는
localRequired, Local native behavior는 provisional practice이고 둘 다 strong event 0이다. Web Day 1 output strong event와 legacy migration event 2건은 Local import·재내보내기·Web reload 뒤에도 Web runtime identity를 유지한다. AppContainer broker source, 공유 ACL 회수와 cold package 경합을 포함한 현재 Windows 11 직접 경계 검증과 설치형 WebView2의 Web-origin archive Local import·reload·re-export는 green이다. 그래도 전부의independentReview는 pending이고 승인 수는 0이며 목표 Windows 10 설치본 conformance, 실제 공개 Web export에서 시작하는 Web-to-Local-to-Web round trip과 독립 author review가 없으므로 전체 scheduler 또는 mastery 완료로 부르지 않는다. - delayed retrieval은
occurredAt표시 시각만으로 열지 않는다. canonicalCreditGranted의evidenceTime과 최초appendReceiptAt이 모두 최소·최대 window를 만족해야 하며, 둘의 경과 차이가 5분을 넘거나 어느 시간축이 역행하면ClockAnomaly를 기록하고 credit을deferredCreditEventIds에 둔다. 이때 outcome은reviewDue로 남아 새 retrieval을 요구한다. archive import는 원래 두 시각을 보존하고MigrationImported.occurredAt만으로 projection clock을 전진시키지 않는다.
- 레슨 상단은
intro.direction,intro.benefits,intro.diagram을 읽어 무엇을 공부하는지, 왜 유용한지, 전체 흐름을 보여준다. intro.diagram.steps는 제품 화면에서 레슨별실무 흐름으로 렌더링한다.목표/개념/스니펫/실행같은 고정 단계가 아니라 해당 YAML의 섹션 제목과 실제 작업 흐름을 반영해야 한다.diagram.runtime은 로컬 실행 환경과 완료 기준을 보존하는 데이터 계약이며, 화면은 필요한 경우에만 이를 보조 정보로 사용한다.- 섹션 하나가 학습카드 하나다. 이 섹션 단위 학습카드 원칙 때문에
sections[].blocks[]의 작은 카드 반복을 기본 구조로 삼지 않는다. - 섹션 흐름은
title → subtitle → goal → why → explanation → tips → snippet → exercise → result → automatic feedback순서로 이어진다. 실행과 별도로검증,완료,제출을 다시 누르게 하지 않는다. - 카드 내부 정보는 라벨, 구획선, 여백으로 구분한다. 카드 안에 또 카드가 덕지덕지 쌓이는 구조는 피한다.
sectionContract:*로 materialize된 신규 섹션은 작은 block card를 반복 렌더링하지 않고, 하나의 섹션 카드 안에서 예제 스니펫, 직접 입력 실습, 실행 결과, 검증/피드백을 흐름형 band로 보여준다.- 카드 헤더와 본문은 같은 제목을 반복하지 않는다. legacy list/prose block에서 첫 목록 항목이나 첫 markdown heading이 셀 제목과 같으면 렌더러가 한 번만 보이게 정리한다.
snippetband는 코드임을 명확히 알 수 있는 박스와예제 스니펫라벨, 우측 상단 복사 버튼을 가진다.- 섹션 헤더 번호는 타이틀/서브타이틀 묶음과 높이를 맞춘다. 셀 도움 요청 액션은 hover-only가 아니라 항상 보이는 control로 둔다.
- 셀 TOC는 push rail이다. overlay flyout으로 같은 아이콘 목록을 한 번 더 띄우지 않는다.
- structured 섹션의
exerciseband는 클릭해야 열리는 preview가 아니라 바로 보이는 실제 입력 editor를 가진다.learning-card-browsergate는 이 editor가 desktop/mobile에서 보이고 starter code를 렌더링하는지 확인한다. - 레슨 overview는
data-learning-overview,data-learning-overview-partmarker를 가진다.learning-card-browsergate는 방향과 학습 효과가 desktop/mobile 화면에 보이는지 확인한다. - structured section은 브라우저 검증을 위해
data-learning-section-card,data-learning-section-structured,data-learning-section-part,data-learning-check-result,data-learning-check-evidencemarker를 가진다. 검증 대상 part는overview,snippet,exercise,result, automatic feedback이다. - marker 계약과 editor build는
uv run python -X utf8 tests/run.py gate learning-card-contract로 확인한다. 실제 데스크톱/모바일 브라우저 렌더링은uv run python -X utf8 tests/run.py gate learning-card-browser로 확인한다. snippet은 예제 스니펫 셀로,exercise.starterCode는 학습자가 직접 입력/수정하는 실습 셀로 materialize한다. 렌더러는 이 영역을직접 입력 실습흐름 안의 실제 에디터로 바로 보여주고, 중복 코드 라벨이나 작성자 배지를 붙이지 않으며data-learning-exercise-input-role="student-practice"를 유지한다.meta.packages는 런타임 패키지 preflight의 1차 입력이다. 코드 import 추론은 보조 수단이다.- 정적 Web의 코드 자동완성은
shouldUseApi()가 false이면 backend/api/ai/complete를 호출하지 않는다. Web 학습 실행과 strong check는 backend 연결 없이 끝나야 한다.
- Codaro 기본 의존성은 제품 실행에 필요한 최소 패키지만 가진다. 학습 주제별 패키지는
pyproject.toml에 넣지 않고 레슨 YAML의meta.packages에 선언한다. meta.packages는 해당 레슨을 열고 실행할 때 필요한 패키지 목록이다. 트랙 전체에서 언젠가 쓸 수 있는 패키지를 미리 모두 넣지 않는다.- 외부 패키지가 필요한 레슨의
intro.diagram.runtime에는 uv 준비 흐름을 드러낸다. 예:라이브러리 확인,uv로 누락 설치,셀 실행/검증. - 소개 레슨은 첫 실행 경험에서 import 확인과 작은
assert검증을 포함해 "필요할 때 준비하고 바로 실행한다"는 감각을 만든다. - 패키지 흐름은 항상
packages-check → packages-install(필요할 때만) → cell-call이다. 설치 성공 또는 이미 준비됨 결과가 없으면 실행으로 넘어가지 않는다. - 레슨 본문에는 직접
pip install안내를 쓰지 않는다. 설치는 제품 capability와 uv 경로가 맡는다.
기존 curriculum은 sections[].blocks[]를 계속 허용한다. 다만 materializer는 legacy blocks를 읽더라도 learningContract와 sectionContract payload를 함께 만든다.
structuredPrimary: true가 붙은 섹션은 structured fields가 1차 학습 계약이고, blocks는 표, 영상, 링크, 추가 설명 같은 보조 자료다. 이 경우 materializer는 structured section card를 먼저 만들고, 남은 blocks를 뒤에 이어 붙여 원본 자료를 잃지 않는다.
새 YAML에서 blocks가 없고 structured section fields가 있으면 materializer가 설명, 스니펫, 실습, 검증 셀을 직접 생성한다.
새 structured section이 subtitle, goal, why, explanation, tips, snippet, exercise.prompt, exercise.starterCode, check 중 일부를 빠뜨리면 materializer는 이를 조용히 추측하지 않는다. sectionContract.contractGaps, sectionContractGaps, contractGapCount, contractGaps로 누락 필드를 보고한다. teacher가 생성하는 신규 YAML과 golden provider run은 contractGapCount: 0이어야 한다.
제품 섹션 카드는 내부 contract gap 경고 band를 기본 노출하지 않는다. 누락 필드는 teacher/tool 결과와 검증 리포트에서 다루고, 사용자 화면에는 YAML 계약 보강 필요 같은 작성자용 문구나 data-learning-section-contract-gaps 표식을 흘리지 않는다.
- Python SSOT 모델:
src/codaro/curriculum/sectionContract.py - Backend materializer:
src/codaro/curriculum/converter.py - Frontend registry mirror:
editor/src/lib/curriculaRegistry.ts - Section renderer:
editor/src/components/curriculum/curriculumSurface.tsx - Teacher tool contract:
src/codaro/ai/toolDefinitions/workbench.py - Eval harness:
src/codaro/ai/teacher/evalHarness.py
프론트 상태, document model, runtime result, trace/workloop은 서로 직접 참조하지 않는다. YAML contract는 document payload로 들어가고, runtime result는 셀 실행 결과로만 붙는다.
teacher/provider loop의 golden case는 다음을 확인해야 한다.
write-curriculum-yaml결과 document에learningContract또는sectionContract가 존재한다.- 섹션 카드가 goal, why, explanation, tips를 contract에서 읽는다.
- golden provider run은
section블록 뒤에sectionContract:explanation → sectionContract:snippet → sectionContract:exercise → sectionContract:check가 같은 섹션 범위 안에서 materialize됐는지 확인한다. - golden provider run은 가능한 한 실제
write-curriculum-yaml핸들러를 통과해야 한다. synthetic trace만으로 통과시키지 않고, 결과 document가 에디터에 로드됐는지(loadedInEditor)와meta.packages가 document runtime packages에 보존됐는지 확인한다. - golden provider run은
contractGapCountresult signal을 확인하고, 새 structured YAML에 누락 필드가 남으면 실패해야 한다. learning-card-contractgate가 structured section marker와 editor build를 고정해야 한다.learning-card-browsergate가 실제yamlToDocumentmaterializer 산출물의 contract flow와 runtime package를 먼저 검증한 뒤, 같은 산출물의 렌더링 필드를 custom curriculum으로 주입해 Playwright CLI로 데스크톱과 모바일 structured section card 흐름과 실습 입력 셀/실행 결과/검증 구역 겹침 여부를 확인한다.learning-card-browsergate는 불완전한 structured section도 포함해 contract gap 경고가 desktop/mobile 제품 카드에 새지 않는지 확인한다.- 패키지 흐름은
packages-check → packages-install(필요할 때만) → cell-call순서를 지킨다. - trace/workloop에는
커리큘럼 YAML 전개,라이브러리 확인,uv 라이브러리 설치,셀 실행/검증같은 사용자가 읽을 수 있는 단계가 남는다. - 커리큘럼 작성 절차는 [[curriculum-authoring]]을 따른다. 특히 새 트랙의 소개 레슨은 이 과정에서 무엇을 만들 수 있는지, 어떤 준비가 필요한지, 완료 후 어떤 산출물을 만들 수 있는지를 보여줘야 한다.
- 질문이 필요할 때만 1-3개 핵심 질문을 제안하고, 바로 생성하지 않고 현재 작업 기준이 trace/workloop에 남는다.
- [[frontend-product-surface]]
- [[teacher-tool-loop]]
- [[curriculum-registry]]
- [[curriculum-authoring]]