Skip to content

Commit ecbd0fe

Browse files
committed
feat: Windows 학습 검사 격리 경계 구현
상황: - 03 W0 Local behavior 검사는 자식 프로세스 감사 훅만 사용해 practice로 제한됐고 launcher 격리 broker가 없었다. - 목표 Windows 10 설치본과 사람 검수는 아직 없으므로 packet 종료와 strong 승격은 금지된다. 변경: - capability 0 AppContainer, Job Object, handle allowlist, named pipe ACL·HMAC·frame 계약을 구현했다. - 관리 runtime·worker 검증, runtime tree hash, 실행별 ACL receipt와 시작 복구를 추가했다. - Python client routing, secret zeroization, infrastructure retry와 계약 소유권·회귀 테스트를 연결했다. - vertical slice 감사를 현재 feasibility 정책과 일치시키고 문서·R10 사실 증거를 갱신했다. 영향: - 설치형 Windows launcher는 local check를 운영체제 격리 경계로 보낼 수 있다. - launcher 밖 fallback은 provisional로 남고, 목표 Windows 10·공유 runtime ACL 회수·cold package·사람 증거가 없어 03 TODO는 유지된다. 검증: - cargo test -p codaro-launcher -- --test-threads=1: 66 passed - cargo check -p codaro-launcher - targeted Python contracts/curriculum/product: 23 passed - vertical slice·sandbox feasibility 감사 통과 - root-clean·docs·backend preflight: 3/3, backend 1604 passed - plan-quality 및 install-launcher-smoke 통과
1 parent b87a37b commit ecbd0fe

32 files changed

Lines changed: 2811 additions & 131 deletions

File tree

contracts/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
| `imageArtifactDescriptor.schema.json` | PNG·JPEG·GIF의 media type·크기·hash 계약 | Local/Web strong check |
2121
| `learningArchive.schema.json` | document·draft·virtual FS·package·evidence 실제 bytes와 lineage를 묶는 닫힌 archive v2 계약 | Web export와 Local atomic import |
2222
| `publicLearningCatalog.json` | 472개 canonical LessonRef의 browser/local tier, eligible path, strong CheckSpec 공개 계약 | Landing lesson generator와 공개 route |
23+
| `checkSandboxBroker.schema.json` | Local 검사 요청·응답, nonce, HMAC, frame 제한을 고정한 닫힌 broker 계약 | Python client와 Windows launcher AppContainer broker |
2324
| `runRouteState.schema.json` | 공개 레슨, Web Run, Local 사이의 lesson identity, path, runtime, durable history 계약 | Landing handoff와 공용 editor route adapter |
2425
| `webCompatibilityC0.json` | 기존 `/codaro/app/` 제품 tree의 source·build·hash·response type 고정 계약 | Pages C0 build, C1 조립, deployed crawl |
2526

contracts/artifactOwners.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ artifacts:
5858
- tests/product/checkSandboxCapabilityProbe.py
5959
- tests/product/verifyCheckSandboxFeasibility.py
6060
- mainPlan/astryx-product-experience/02-learning-method/README.md
61+
- artifactId: check-sandbox-broker
62+
role: source
63+
owner: learning-runtime
64+
sourcePath: contracts/checkSandboxBroker.schema.json
65+
surfacePaths:
66+
- src/codaro/curriculum/checkSandboxBrokerClient.py
67+
- launcher/codaro-launcher/src/check_broker.rs
68+
- launcher/codaro-launcher/src/check_sandbox.rs
69+
- tests/contracts/testCheckSandboxBrokerContract.py
6170
- artifactId: run-route-state
6271
role: source
6372
owner: product-architecture
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "https://codaro.dev/contracts/checkSandboxBroker.schema.json",
4+
"title": "Codaro Check Sandbox Broker",
5+
"oneOf": [
6+
{ "$ref": "#/$defs/RequestEnvelope" },
7+
{ "$ref": "#/$defs/ResponseEnvelope" }
8+
],
9+
"$defs": {
10+
"Hex256": {
11+
"type": "string",
12+
"pattern": "^[0-9a-f]{64}$"
13+
},
14+
"Nonce": {
15+
"type": "string",
16+
"pattern": "^[0-9a-f]{32}$"
17+
},
18+
"Environment": {
19+
"type": "object",
20+
"maxProperties": 32,
21+
"propertyNames": {
22+
"pattern": "^[A-Za-z_][A-Za-z0-9_]*$"
23+
},
24+
"additionalProperties": {
25+
"type": "string",
26+
"maxLength": 32768
27+
}
28+
},
29+
"Request": {
30+
"type": "object",
31+
"additionalProperties": false,
32+
"required": [
33+
"schemaVersion",
34+
"runId",
35+
"fixtureRoot",
36+
"packagePaths",
37+
"environment",
38+
"timeoutMs",
39+
"workerRequest"
40+
],
41+
"properties": {
42+
"schemaVersion": { "const": 1 },
43+
"runId": {
44+
"type": "string",
45+
"pattern": "^[0-9a-f]{32}$"
46+
},
47+
"fixtureRoot": {
48+
"type": "string",
49+
"minLength": 1,
50+
"maxLength": 32768
51+
},
52+
"packagePaths": {
53+
"type": "array",
54+
"maxItems": 16,
55+
"uniqueItems": true,
56+
"items": {
57+
"type": "string",
58+
"minLength": 1,
59+
"maxLength": 32768
60+
}
61+
},
62+
"environment": { "$ref": "#/$defs/Environment" },
63+
"timeoutMs": {
64+
"type": "integer",
65+
"minimum": 250,
66+
"maximum": 15000
67+
},
68+
"workerRequest": {
69+
"type": "object"
70+
}
71+
}
72+
},
73+
"Response": {
74+
"type": "object",
75+
"additionalProperties": false,
76+
"required": [
77+
"schemaVersion",
78+
"runId",
79+
"executor",
80+
"workerResponse",
81+
"infrastructureError"
82+
],
83+
"properties": {
84+
"schemaVersion": { "const": 1 },
85+
"runId": {
86+
"type": "string",
87+
"pattern": "^[0-9a-f]{32}$"
88+
},
89+
"executor": { "const": "windows-appcontainer" },
90+
"workerResponse": {
91+
"type": ["object", "null"]
92+
},
93+
"infrastructureError": {
94+
"type": ["string", "null"],
95+
"minLength": 1,
96+
"maxLength": 8192
97+
}
98+
}
99+
},
100+
"RequestEnvelope": {
101+
"type": "object",
102+
"additionalProperties": false,
103+
"required": ["schemaVersion", "direction", "nonce", "payload", "mac"],
104+
"properties": {
105+
"schemaVersion": { "const": 1 },
106+
"direction": { "const": "request" },
107+
"nonce": { "$ref": "#/$defs/Nonce" },
108+
"payload": { "$ref": "#/$defs/Request" },
109+
"mac": { "$ref": "#/$defs/Hex256" }
110+
}
111+
},
112+
"ResponseEnvelope": {
113+
"type": "object",
114+
"additionalProperties": false,
115+
"required": ["schemaVersion", "direction", "nonce", "payload", "mac"],
116+
"properties": {
117+
"schemaVersion": { "const": 1 },
118+
"direction": { "const": "response" },
119+
"nonce": { "$ref": "#/$defs/Nonce" },
120+
"payload": { "$ref": "#/$defs/Response" },
121+
"mac": { "$ref": "#/$defs/Hex256" }
122+
}
123+
}
124+
}
125+
}

docs/skills/architecture/learning-yaml-contract.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ check:
9898
normalization: trim-final-newline
9999
```
100100

101-
fixture 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 자식 프로세스에서 실행해 fixture 밖 읽기·쓰기, network, child process, dynamic code, timeout을 거부하지만 Windows AppContainer broker가 없으므로 결과를 practice 피드백으로만 제공하고 strong evidence를 append하지 않는다. behavior 판정은 file·directory·table·image descriptor와 package SRI를 계속 계산하지만 AppContainer conformance 전에는 공용 evidence payload로 승격하지 않는다. 임의 원격 URL이나 최신 버전 즉석 설치는 strong evidence에 사용할 수 없다.
101+
fixture 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, per-run ACL receipt·startup 회수로 fixture 밖 읽기, network와 child process를 OS 경계에서도 거부한다. 다만 목표 Windows 10 22H2 설치본 conformance와 shared runtime ACL receipt·GC가 없으므로 결과를 practice 피드백으로만 제공하고 strong evidence를 append하지 않는다. behavior 판정은 file·directory·table·image descriptor와 package SRI를 계속 계산하지만 release conformance 전에는 공용 evidence payload로 승격하지 않는다. 임의 원격 URL이나 최신 버전 즉석 설치는 strong evidence에 사용할 수 없다.
102102

103103
강한 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로 부르지 않는다.
104104

@@ -112,7 +112,7 @@ fixture hash는 key를 정렬한 compact JSON UTF-8 bytes의 SHA-256 SRI다. aut
112112
- due 여부를 확인하거나 문제를 펼치는 별도 버튼을 만들지 않는다. route 진입과 evidence 갱신 시 queue가 자동 재계산된다.
113113
- transfer 또는 retrieval strong event가 저장되면 같은 check ID의 due 카드는 queue에서 빠진다.
114114
- variant 배열이 존재하거나 ID가 생성됐다는 사실은 학습 evidence가 아니다. strong executor 실제 통과와 append-only event가 있어야 실행 증거로 계산한다.
115-
- 현재 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를 유지한다. 전부의 `independentReview`는 pending이고 승인 수는 0이며 cold package 준비, Windows AppContainer, 실제 설치본 round trip과 독립 author review가 없으므로 전체 scheduler 또는 mastery 완료로 부르지 않는다.
115+
- 현재 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와 현재 Windows 11 직접 경계 검증은 존재하지만 전부의 `independentReview`는 pending이고 승인 수는 0이며 cold package 준비, 목표 Windows 10 설치본 conformance, 실제 설치본 round trip과 독립 author review가 없으므로 전체 scheduler 또는 mastery 완료로 부르지 않는다.
116116

117117
## 렌더링 원칙
118118

docs/skills/ops/foundation/testing-and-gates.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ uv run python -X utf8 tests/run.py gate attempts
234234
- `product-browser-webview2-evergreen`은 Windows에서 현재 editor를 build하고 current-commit wheel을 격리 launcher root에 설치한 뒤 실제 `codaro-launcher.exe`의 네이티브 WebView2 창을 연다. 제품 데이터는 gate 전용 `CODARO_HOME`에 격리하므로 개발자의 실제 사용자 데이터를 초기화하거나 읽지 않는다. CDP는 별도 브라우저를 띄우는 용도가 아니라 test process가 `CODARO_WEBVIEW2_TEST_BROWSER_ARGUMENTS`로 해당 WebView2 document를 관찰하는 데만 쓰며, 일반 실행에서는 이 test-only 인수를 설정하지 않는다. Win32 client 크기와 `innerWidth/innerHeight × devicePixelRatio`를 대조하고 Local Home 900x640, Notebook 1024x768, Automation 1440x900의 runtime tier, 가로 overflow, 상단 테마·공용 SNS, control overlap과 계좌번호 후원 dialog를 검사한다. Automation은 격리 fixture의 scheduled, running, succeeded, failed와 route된 live paused, health probe가 감지한 disconnected를 각각 1440x900으로 캡처한다. failed capture는 실패 원인, `summary.json` artifact, 활성 E-Stop 이유를 함께 보존하고 여섯 화면 모두 Windows/macOS/Linux 사용자 path, 비예제 email, access credential visible-text 신호가 0이어야 한다. Notebook은 12개 셀로 긴 문서를 만든 뒤 CodeMirror와 Markdown textarea의 문서 경계 `↑`·`↓`로 첫 셀과 마지막 셀을 왕복하고 선택 편집기의 DOM focus 및 viewport 노출을 확인한다. 같은 설치본에서 Windows 한국어 두벌식 입력기에 네이티브 virtual-key를 보내 `한글` 조합의 start/update/end와 조합 중 방향키 셀 유지, 조합 확정 뒤 경계 이동을 CodeMirror와 Markdown 양쪽에서 확인한다. 첫 코드 셀을 실제 실행한 뒤 WebView2 Chromium accessibility tree에서 `노트북 셀` list, 1~12번 listitem·편집기, 순번이 포함된 실행 결과, 셀 작업, 문서 뒤 `노트북 셀 추가` toolbar 순서를 검사한다. Local Home은 실제 Tab 순서로 주요 탐색 이름을 확인하고 WebView2 CDP의 forced-colors emulation에서 공용 control 경계와 focus outline을 캡처한다. 이어 실제 편집 가능한 exercise 셀을 포함한 Web-origin 학습 archive를 가져와 화면 반영, reload 복원, Web runtime 증거 보존, virtual FS·package·automation draft 보존, disabled·unscheduled 작업 채택과 workspace 경계, 재내보내기 후 의미상 동일한 payload를 확인한다. `CODARO_DEPLOYED_WEB_URL`이 주어지면 system Edge의 새 context가 실제 공개 Lesson에서 정답 코드를 편집·실행하고 browser strong check와 IndexedDB 근거 저장을 기다린 뒤 제품 설정의 학습 작업 파일을 내려받는다. 이 파일을 같은 gate의 설치형 Local에 가져와 root hash·runtime identity·초안 reload·source evidence set·재내보내기 payload를 확인한다. Pages workflow는 deploy job의 실제 `page_url`을 Windows 후속 job에 넘기므로 배포 전 source fixture로 이 경로를 대신할 수 없다. evidence manifest의 재생성 시각과 Local evidence set union 때문에 재내보내기 전체 root hash는 달라질 수 있어 document·drafts·virtual FS·packages·automation drafts와 source evidence event를 materialize해 직접 비교한다. screenshot과 WebView2 exact version은 `output/test-runner/product-browser-webview2-evergreen/webview2-product-smoke-report.json`에 남긴다. launcher CI cache는 실제 workspace target인 `launcher/target/`과 `launcher/Cargo.lock`을 키로 쓰고, WebView2 job은 gate 전용 cargo target을 같은 lock hash로 재사용한다. 공개 배포 경로까지 green이어도 Windows 10 22H2 self-hosted Fixed Version lock, 수동 NVDA·Narrator 발화 청취, 200%·400% zoom을 대신하지 않는다.
235235
- `product-experience-browser`는 새 Landing·Editor build, 정적 Web 서버와 실제 Local API를 함께 띄워 Chromium 72-case를 실행한다. 320·390·900·1024·1440px에서 Landing·Learn, Web/Local 학습 홈·Lesson·Chat·Run·Automation과 Web-to-Local handoff의 overflow, 겹침, console/asset 오류, image, accessible name, Astryx scope·runtime tier를 확인한다.
236236
- Web evidence는 Day 1 mastery·자동 transfer·24시간 retrieval, Day 2·11·15·19·20·22·27·30 다중 입력, Seaborn table/image, pathlib·zip·schedule 각 4개 flow를 실제 pyproc Worker에서 실패 답안 뒤 수정 답안 순서로 검사한다. 검증 cache와 append-only IndexedDB event는 reload 뒤 유지되지만 `completedAt`은 만들지 않는다.
237-
- Local provisional check는 성공한 커널 결과를 native `local-sandbox`에서 같은 CheckSpec으로 재검증하되 Windows AppContainer conformance 전에는 `data-learning-check-evidence=practice`로 표시하고 SQLite strong append를 0으로 유지한다. infrastructure failure만 250ms 뒤 한 번 재시도하며 mismatch와 학생 코드 오류는 재시도하지 않는다.
237+
- Local provisional check는 성공한 커널 결과를 native `local-sandbox`에서 같은 CheckSpec으로 재검증한다. Windows launcher source는 AppContainer capability 0, Job Object, handle allowlist, HMAC named pipe, managed runtime tree hash와 per-run ACL receipt·startup 회수를 적용하며 `launcher-test`가 현재 Windows에서 fixture 쓰기와 외부 파일·network·child process 차단을 실기동한다. 목표 Windows 10 22H2 설치본 conformance와 shared runtime ACL receipt·GC 전에는 `data-learning-check-evidence=practice`로 표시하고 SQLite strong append를 0으로 유지한다. infrastructure failure만 250ms 뒤 한 번 재시도하며 mismatch와 학생 코드 오류는 재시도하지 않는다.
238238
- `local-w0-conformance`는 Web behavior의 Local handoff, Local Day 1·pathlib·zip·schedule의 provisional 성공, Local strong event 0건을 확인한다. Web strong·migration 2건을 Local로 가져와 재내보내고 reload해도 Web runtime identity와 event set이 유지돼야 한다.
239239
- archive는 manifest/event/payload hash, non-credit migration, metadata backup, 중복·tamper·conflict·legacy 이관을 검사한다. report와 screenshot은 68개 대표 case의 증거이며 전체 engine·WebView2·AppContainer·수동 screen-reader·전체 virtual FS/package matrix를 대신하지 않는다.
240240
- `dogfood-alpha-audit`는 첫 실행부터 provider 연결, 질문, clarification, `resolve-learning-goal``search-curricula``compose-master-plan` 추천·조합, gap-only YAML 생성, 학습카드 연습, 실행·피드백·실패 복구 경로가 문서와 코드 gate로 연결되어 있는지 확인한다. report 점수는 `scoreKind: wiring-coverage`이며 `completionEligible: false`, `learningStrongCompletionCovered: false`를 기록하므로 학습 완주 점수로 쓰지 않는다. `output/test-runner/dogfood-alpha-audit/dogfood-alpha-report.json``status`, `summary`, `requirementFailures`, `gitHead`, `startedAt`/`completedAt`, `durationMs``quality-cycle``payloadGitHead` evidence로 대조한다. 제품 품질 판단은 이 wiring audit, 실제 product browser matrix, strong learning evidence, live provider credential 환경의 `ai-live-smoke`가 모두 있어야 한다.

editor/src/lib/generatedContracts/artifactOwnership.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// Generated by docs/skills/ops/tools/genProductContracts.py.
22
// Source SHA-256: 4679b9c14619d69672ff06da0291dd45c71b100ee6e8a0b5698fb96cd55eb113
3-
// Owners SHA-256: e8f528b70a269a926fa4e3737a7d2ff284a7a37cfb9697d5d02ef798dea4d4b1
3+
// Owners SHA-256: ac8031dd9d74489afae8f755033c66df5820500e36b65271a302c8105a68eb54
44
export const ARTIFACT_OWNERSHIP_CONTRACT_SHA256 = "4679b9c14619d69672ff06da0291dd45c71b100ee6e8a0b5698fb96cd55eb113" as const;
5-
export const ARTIFACT_OWNERSHIP_OWNERS_SHA256 = "e8f528b70a269a926fa4e3737a7d2ff284a7a37cfb9697d5d02ef798dea4d4b1" as const;
5+
export const ARTIFACT_OWNERSHIP_OWNERS_SHA256 = "ac8031dd9d74489afae8f755033c66df5820500e36b65271a302c8105a68eb54" as const;
66

77
export type ArtifactRole = "source" | "generated" | "packaged" | "evidence";
88

launcher/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)