Skip to content

Commit 79df851

Browse files
committed
엔진 독립 연구 종합: engine-independence 이니셔티브(PRD) 신설
소유자 질문(Pyodide 의존 제거·독립·더 훌륭한 방법)에 대한 전문 에이전트 2종(생태계 리서치 + 코드 결합 감사) 연구를 PRD로 확정한다. - 결론: 완전 제거는 지금 손해(모트 = FFI·패키지 생태·스냅샷 재작성, 수개월). 대신 갇히지 않는 사다리: P0 자가 호스팅(유통 독립) -> P1 EngineContract seam(코드 독립, ~250 LOC 동작 무변경 설계 확보) -> P2 스냅샷 사전 제조 probe -> P3 업스트림 워치/기여 -> P4 조건부 fork 보험(MPL, Cloudflare 코드 공개). - 핵심 증거: PEP 783 Accepted(pyemscripten 휠 = 런타임 비종속 표준, 미래 독자 런타임에도 휠 이식 보장), Pyodide의 CPython 패치 9개 수준, WASI는 동적 C 확장 불가로 현재 양립 불가. - 결합 감사 판정: 리액티브(접점 0)/세션/.pymachine/SW/스케줄러는 이미 엔진 중립. 계약 실태 표에 암묵 FFI/fetch 가정 행 신설, 스냅샷 행에 대응 계획 연결. 원장에 결정 기록 + 소유자 전용 TODO(npm 퍼블리시, 데모 연결, 착수 승인) 명시.
1 parent 514439e commit 79df851

4 files changed

Lines changed: 101 additions & 4 deletions

File tree

mainPlan/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@
2424
| [web-python-runtime/](web-python-runtime/) | 운영 체계 + 수명주기/soundness/시스템콜 v1 승격 (2026-07-11) | 서버 없이 브라우저 탭에서 도는 진짜 런타임 파이썬. codaro/dartlab/xlpod 공유 SSOT. |
2525
| [local-parity/](local-parity/) | 개시 (2026-07-11) | 실행·터미널·라이브러리에서 로컬 파이썬과 구분 불가능해지는 발명 프로그램. |
2626
| [browser-os/](browser-os/) | 개시 (2026-07-12) | 로컬에도 없는 것: 꺼지지 않고 파일 하나로 이동하는 파이썬 컴퓨터(머신 이미지·영속 디스크·셸·수명주기). |
27+
| [engine-independence/](engine-independence/) | PRD, 착수 승인 대기 (2026-07-12) | Pyodide에 갇히지 않는 사다리: 자가 호스팅 -> EngineContract seam -> 스냅샷 사전 제조 -> 조건부 fork 보험. |
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# engine-independence - 엔진 독립: Pyodide 없이도 살 수 있는 pyproc
2+
3+
상태: PRD (2026-07-12, 전문 에이전트 2종 연구 종합: 생태계 리서치 + 코드 결합 감사). **착수는 소유자 승인 대기.**
4+
5+
## 질문과 답
6+
7+
소유자 질문: "Pyodide 의존성을 제거하고 독립적으로, 지금 모든 게 가능한 방법 - 아니면 더 훌륭한 방법은?"
8+
9+
답: **완전 제거는 지금 하면 손해다**(수개월 재작성 + 생태계 상실, 아래 근거). 더 훌륭한 방법이 실재한다:
10+
의존을 4개 층으로 분해하면 층마다 오늘 놓을 수 있는 독립 장치가 있고, 그 사다리를 다 놓으면
11+
"Pyodide가 내일 방향을 틀어도 pyproc은 안 죽는" 상태가 된다. 떠나는 게 아니라 **갇히지 않게 만드는 것**이 정답.
12+
13+
| 의존 층 | 독립 장치 | 비용 |
14+
|---|---|---|
15+
| 유통 (CDN에서 자산 로드) | P0 자가 호스팅(핀 자산 셀프 서빙) | 일 단위 |
16+
| 코드 (엔진 API 산재) | P1 EngineContract seam(어댑터 1파일로 격리) | 주 단위 |
17+
| 기능 (스냅샷 벽 #5195) | P2 스냅샷 사전 제조 probe + P3 업스트림 워치/기여 | 주 단위 |
18+
| 미래 (엔진 교체 옵션) | P4 fork 보험(MPL, 조건부) + PyEmscripten ABI 탈출구 | 발동 시에만 |
19+
20+
## 왜 "완전 제거"가 오늘 손해인가 (증거)
21+
22+
1. **재구현 대상이 인터프리터가 아니라 글루다.** Pyodide의 CPython 패치는 9개 파일 수준(PEP 776 업스트리밍의 결과). 떠난다는 것은 FFI(JsProxy/PyProxy/run_sync) + 로더/dylink + micropip/lock + 스냅샷 기계를 재작성한다는 뜻 = 사실상 pyodide core 재작성(수개월), 얻는 것은 거의 없음.
23+
2. **생태계가 우리에게 유리한 방향으로 표준화 중.** CPython 3.14가 Emscripten을 Tier 3로 공식 복귀시켰고(PEP 776, Active), **PEP 783이 Accepted(2026-04-06)**: C 확장 휠이 `pyemscripten_2026_0_wasm32` 태그로 PyPI에 정식 업로드된다. 이 플랫폼 정의는 "Python/Pyodide에 관한 내용이 없다"고 명시 = **미래에 독자 런타임을 만들어도 휠 자산이 이식된다. 지금 안 떠나도 갇히지 않는 근거.** cibuildwheel 4.0이 이미 이 ABI를 빌드한다.
24+
3. **대안 엔진은 오늘 우리 요구와 양립 불가.** WASI 레인은 동적 C 확장이 막혀 있고(cpython#142234 open, component-model#401 open) PEP 816이 WASI 0.2를 건너뛰겠다고 자인 = numpy/pandas 상실. MicroPython-wasm은 진짜지만(303KB, 수 ms 부팅, 공식 npm) CPython 휠 생태계가 없어 **대체가 아니라 보조 프로세스 레인** 재료. RustPython 미성숙.
25+
4. **fork는 공짜 보험이라 선제 불필요.** Pyodide는 MPL-2.0(파일 단위 copyleft, fork 법적 장벽 낮음), 유일한 대규모 fork(Cloudflare)가 스냅샷 제조 코드까지 전부 공개(cloudflare/pyodide + workerd/src/pyodide/make_snapshots.py). 필요해지는 순간 며칠 안에 열 수 있다.
26+
5. **유일하게 "오늘 우리 통제 밖에서 깨질 수 있는" 지점은 CDN이다.** 코드/버전은 핀으로 고정했지만 jsdelivr 가용성·정책은 우리 밖. 이게 P0가 1순위인 이유다.
27+
28+
## 옵션 매트릭스 (기각 근거 보존)
29+
30+
| 옵션 | 유지 | 상실/부담 | 비용 | 판정 |
31+
|---|---|---|---|---|
32+
| 1. 현행(스톡 + 핀) | 전부 | CDN 리스크, 스냅샷 벽 업스트림 종속 | 0 | P0 전까지의 기본값 |
33+
| 2. 자가 호스팅 | 전부 | 자산 서빙만 추가 || **P0 채택** |
34+
| 3. fork + 패치 (Cloudflare 모델) | 전부 + 스냅샷 벽 자체 해결 | 리베이스 유지비 영구(연 1회 ABI break) | 초기 주, 유지 월 누적 | **P4 조건부 보험** |
35+
| 4. 독자 CPython-Emscripten 빌드(ABI 호환) | 휠은 ABI 정합 시 유지(사양 보장) | FFI/로더/micropip/스냅샷 전부 재구현 | 수개월+ | 기각(지금은). ABI 탈출구가 미래 옵션을 보존 |
36+
| 5. WASI 레인 | 표준 정렬, 샌드박스 | **동적 C 확장 불가 = 수치 스택 상실** | 수개월 + 능력 후퇴 | 기각(WASI 0.3 + dlopen 해결 시 재평가) |
37+
| 6. MicroPython 보조 엔진 | 초경량 프로세스 레인 추가 | 대체 불가(병행만) || 별도 캠페인 후보(승인 시) |
38+
39+
## 코드 결합 감사 요약 (전수 정독 결과)
40+
41+
- 엔진 API를 실제로 만지는 파일은 15개 중 **8개, 약 40지점**. 절반은 이동 가능한 부팅/배포 코드.
42+
- 진짜 모트 3덩어리: (1) JsProxy/run_sync FFI (2) micropip/lock/휠 생태 (3) 엔진 스냅샷 API.
43+
- **보석은 이미 엔진 중립**: reactive(엔진 접점 0), session/.pymachine, SW 가상 오리진, PyProc 스케줄링, SharedKernel 클라이언트 - 전부 "선형 메모리 + 스택 포인터 + 결정적 부팅 + 엔진 스냅샷" 4개 프리미티브 위의 순수 알고리즘. 엔진이 바뀌어도 살아남는 자산이 pyproc의 가치라는 뜻.
44+
- 위험 상위 5와 조치: ① `_module` 내부 3경로(memoryCapability) -> P1 seam으로 한 곳에 격리 ② 스냅샷 언더스코어 3종 -> 계약 실태 표 기존 행 + P2/P3 ③ 세션 리플레이 결정성 무대조 -> **해소(2026-07-12, cp0 다이제스트 h0 대조)** ④ interrupt 무증상 no-op -> **해소(2026-07-12, interrupts 플래그 소비)** ⑤ 암묵 FFI 변환(`toJs` 덕타이핑 3개소)·"엔진이 전역 fetch를 쓴다" 가정 -> 계약 실태 표 신규 행.
45+
- 감사가 적발한 실결함: subprocess 자식 워커에 indexURL 미전달(자가호스팅 배포에서 자식만 CDN으로 새는 버그) -> **해소(2026-07-12, `Runtime.indexURL` 계약)**.
46+
47+
## 단계 (사다리, 각 단계에 게이트)
48+
49+
- **P0 유통 독립 - 자가 호스팅** (일 단위): GitHub Releases의 v314.0.2 전체 배포판을 자가 서빙 경로로 옮기고 `boot({indexURL})`로 소비. 게이트: 자가 경로에서 브라우저 게이트 전 검사 GREEN + offlineBoot/swOffline probe 재실측 + docs/consuming 갱신. (자식 워커 CDN 누수는 이미 수정됨.)
50+
- **P1 코드 독립 - EngineContract seam** (주 단위): 감사의 계약안 채택. `src/runtime/engines/pyodideEngine.js`(신규 ~250 LOC, 대부분 기존 코드 이동)로 엔진 접점을 격리하고 Runtime/능력은 계약만 호출. 파이썬측은 어댑터가 `pyprocFfi` shim 주입(runSync 등). 게이트: **동작 무변경** = 구조 + 브라우저 게이트가 수정 없이 GREEN.
51+
- **P2 기능 독립 - 스냅샷 사전 제조 probe** (attempts, envManager 캠페인 질문으로): Cloudflare workerd의 make_snapshots.py 패턴(패키지 사전 주입 후 스냅샷)을 스톡 v314에서 재현 가능한지 실측. 벽 원인은 스냅샷 직렬화기의 기대 hiwire 엔트리 고정(7개)이므로, 업스트림 PR #5971(Load packages without using Python)이 여는 경로를 관찰하며 병행.
52+
- **P3 업스트림 워치/기여**: #5195(벽 본체), #5971(공식 해결 트랙), 스냅샷 안정화 언질(유지보수자, 2025-12-25). fork보다 싼 길 = 이 트랙의 테스트/리뷰 기여로 착지를 앞당기기.
53+
- **P4 조건부 fork (보험)**: 발동 조건 = "스냅샷-with-packages가 제품 차단 요인 + 업스트림 착지 부재"가 둘 다 참일 때만. cloudflare/pyodide(MPL) 패치 차용, **패키지 CI는 인수하지 않는다**(레시피 수백 개가 진짜 유지비. 공식 배포판 자산 재사용).
54+
- 병행 후보(별도 승인): MicroPython-wasm 경량 프로세스 레인(303KB/수 ms 부팅 = 다른 계급의 프로세스). attempts 신규 캠페인로만 개시.
55+
56+
## 연구가 공짜로 준 부수 발견 (즉시 반영/주의)
57+
58+
- 314.0.0에서 `pyodide.asm.js` -> `pyodide.asm.mjs` 개명 + classic worker 지원 중단: pyproc은 module worker + `pyodide.mjs` 경로라 정합(문제 없음). 코어 캐시 MIME은 .js/.mjs 둘 다 이미 커버.
59+
- ssl 모듈 stub화 + OpenSSL 유래 hashlib 제거(314.0.0): 라이브러리 커버리지 17/17은 v314에서 실측된 수치라 유효. ssl 직접 의존 패키지를 커버리지에 편입할 때 주의.
60+
- JSPI가 Chrome 137에 기본 출시 + 표준화 완료: Chromium 전용 전제가 더 단단해짐.
61+
- 업스트림이 pthreads 빌드 실험 중(PR #6285, opt-in): 우리 "워커 N개 = GIL N개" 모델과 별개의 미래 병렬 축. 관찰만.
62+
63+
## 근거 링크 (핵심만)
64+
65+
- PEP 776(Emscripten Tier 3) / PEP 783(pyemscripten 휠, Accepted) / PEP 816(WASI 0.3 대기) / PEP 11(티어 표): peps.python.org
66+
- CPython `Platforms/emscripten`(빌드 가능 + JSPI REPL 예제): github.com/python/cpython
67+
- 스냅샷 벽: pyodide#5195(open) / 해결 트랙 pyodide#5971(open) / 직렬화기의 기대 엔트리 7개 고정(src/js/snapshot.ts)
68+
- Cloudflare 실증: cloudflare/pyodide(fork) + workerd/src/pyodide(make_snapshots.py 등, 전부 공개) + 공식 블로그(패키지 포함 스냅샷, cold start ~1s)
69+
- pyodide-build 단독 사용 가능(xbuildenv 지정): github.com/pyodide/pyodide-build
70+
- MicroPython-wasm 공식 npm: @micropython/micropython-webassembly-pyscript
71+
72+
## 착수 전 재검 (mainPlan 규칙)
73+
74+
- 정합성: P0/P1은 기존 계약(indexURL, 공개 표면) 안에서 움직여 소비자 무영향. P1은 공개 표면 불변이 게이트.
75+
- ROI: P0(일 단위)가 유일한 실시간 리스크(CDN)를 제거하므로 최우선. P1은 이후 모든 엔진 실험의 고정비를 1파일로 낮추는 투자.
76+
- 롤백: P0는 indexURL 되돌리면 끝. P1은 어댑터 도입 커밋 단위 revert 가능(동작 무변경 게이트가 보증).

mainPlan/web-python-runtime/01-architecture.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ pyproc의 코어는 새 이론이 아니라 codaro `tests/_attempts`에서 브
5252
| 페이지 해시 soundness | 실질적 sound(누락 확률 무시 가능) | 이중 32비트(실효 64비트, ~2^-64)로 승격. 비용 1.54배, 30MB 힙 14.3ms 실측 | 해소 (attempts/reactiveSoundness 졸업, 2026-07-11) |
5353
| syscallBridge | input/HTTP/subprocess를 실제로 빌린다 | v1 실배선: input(동기 + JSPI `run_sync`), urllib(동기 XHR, proxyUrl 옵션), subprocess(`["python","-c",code]`, 자식 워커, runAsync 경로). 저수준 socket·requests 계열은 미배선 | v1 해소 (attempts/syscallBridge 졸업, 2026-07-11). 잔여는 local-parity 축 |
5454
| PyProc 오류 경로 | 부팅·행·죽음이 유한 시간에 귀결 | 부팅 실패 reject + `map(.., {taskTimeoutMs})` 행 수렴 + kill/스냅샷 respawn(302ms 실측). 남은 것: 협조적 취소(SIGINT) | 해소 (attempts/processLifecycle 졸업, 2026-07-11). 취소는 후보 |
55-
| Pyodide 스냅샷 API | 스냅샷-fork | `_makeSnapshot`/`_loadSnapshot`은 Pyodide 밑줄(실험) API. 버전 핀(v314.0.2)으로만 안전 | 버전 올릴 때 최우선 재검증 항목 |
55+
| Pyodide 스냅샷 API | 스냅샷-fork | `_makeSnapshot`/`_loadSnapshot`은 Pyodide 밑줄(실험) API. 버전 핀(v314.0.2)으로만 안전. 대응 계획: [engine-independence](../engine-independence/README.md) P1(seam 격리)·P3(업스트림 #5971 워치) | 버전 올릴 때 최우선 재검증 항목 |
56+
| 암묵 FFI/fetch 가정 | 엔진 교체·업데이트에 견딤 | `toJs` 덕타이핑 3개소(terminal/worker/sharedKernelHost), latin1 바이트 밀수(syscallBridge), "엔진이 .whl/코어를 전역 fetch로 받는다" 가정(wheelCache/coreCache)은 변환 기본값·fetch 전략 변경 시 예외가 아니라 틀린 데이터/무증상 캐시 무력화로 나타남 | **열림(부채)**: engine-independence P1 seam이 `toJs`를 계약 메서드로 승격 + dist 서술자로 이동 |
5657
| 리액티브/%undo 메모리 | 장시간 사용에도 안전 | base(힙 전체 사본)가 RAM 상주 + 체크포인트 델타가 무한 누적(%undo는 문장마다). 장수 REPL에서 실메모리 성장 | **열림(부채)**: 델타 rebase/prune 설계 필요 |
5758
| README 표면 동기화 | 공개 표면 = README 표 | `tests/run.mjs`가 index.js의 모든 export가 양쪽 README에 등장하는지 기계 검사(2026-07-12). 표도 전량 갱신됨 | 해소 (가드 상시) |
5859
| 전역 스텁 3종 | 스코프 밖 무영향 | entropy/시간(session 부팅), fetch(wheelCache install 구간) 스왑은 finally 복원되지만 그 창 안의 동시 작업엔 보인다 | 문서화된 트레이드오프. 동시 부팅 금지 명시 필요 |

mainPlan/web-python-runtime/03-progress-ledger.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@
44

55
## 결정 원장 (최신이 위)
66

7+
### 2026-07-12 엔진 독립 연구 + 평가 후속 정비 (전문 에이전트 2종 연구 종합)
8+
9+
- 소유자 질문("Pyodide 의존 제거, 독립, 아니면 더 훌륭한 방법") -> 생태계 리서치 + 코드 결합 감사 에이전트 연구 종합. **결론: 완전 제거는 지금 손해, "갇히지 않는 사다리"(P0 자가호스팅 -> P1 EngineContract seam -> P2 스냅샷 사전 제조 -> P3 업스트림 워치 -> P4 조건부 fork 보험)가 정답.** 정본: [engine-independence PRD](../engine-independence/README.md). 핵심 증거: PEP 783 Accepted(2026-04, pyemscripten 휠 = Pyodide-agnostic 탈출구), Pyodide의 CPython 패치는 9개 파일(모트는 FFI·패키지 생태·스냅샷 3덩어리), Cloudflare fork 코드 전부 공개(MPL = 공짜 보험), WASI는 동적 C 확장 불가로 현재 양립 불가.
10+
- 결합 감사 판정: 보석(리액티브 접점 0, 세션/.pymachine, SW 층, PyProc 스케줄링, SharedKernel)은 이미 엔진 중립 - "선형 메모리 + 스택 포인터 + 결정적 부팅 + 엔진 스냅샷" 4프리미티브 위 순수 알고리즘. EngineContract seam 설계(~250 LOC, 동작 무변경) 확보.
11+
- **감사 적발 결함 3건 즉시 수정**: ① subprocess 자식 워커에 indexURL 미전달(자가호스팅/오프라인에서 자식만 CDN으로 새는 누수) -> `Runtime.indexURL` 계약 신설 ② `PyProc.interrupt`가 SIGINT 미지원 워커에도 true 반환(무증상 no-op) -> ready의 interrupts 플래그 소비 ③ 세션 리플레이 결정성 무대조(엔진/엔트로피 변화로 cp0이 달라져도 델타를 덮어 조용한 오염) -> 저장물에 cp0 다이제스트(h0) 포함 + load/openMachine 대조 예외(구 저장물은 무검사 통과, .pymachine에도 적용).
12+
- 평가 후속 정비 완료: README 표면 동기화 기계 가드(부채 해소), 데모 배포 준비물(루트 _headers + docs/operations/demoHosting.md), CI 실측 JSON 아티팩트(PYPROC_GATE_OUT), 레포 설명/토픽. 계약 실태 표에 "암묵 FFI/fetch 가정" 행 신설.
13+
714
### 2026-07-12 발명 라운드 4: uv 레인 + SW 계층 + 공유 커널 (probe 7종 GREEN, 게이트 26검사)
815

916
- 타 세계 개념 흡수 라운드(소유자 지시: "판을 바꿀 아이디어 + 개념부터 확실히"). 새 캠페인 envManager 개설(캠페인 3개째: 개념 = 환경의 선언·캐시·재현).
@@ -95,10 +102,22 @@
95102

96103
## NEXT (재개 지점)
97104

98-
1. **다음 발명 라운드(local-parity NEXT)**: 델타 체인 영속 + 분기 그래프 + 세션 간 커널 복원, "웹의 uv"(wheel OPFS 캐시 + requirements manifest). 전부 runtimeParity 캠페인 안에서 probe로.
99-
2. ~~푸시 후 CI 첫 실행 확인~~ 완료(2026-07-12, 사고 기록): 확인을 방치한 대가로 **전 이력 적색**이었다. 원인은 러너 특이사항이 아니라 로컬-CI 게이트 불일치: docs 2곳이 git 미추적 로컬 규칙 문서를 상대 링크로 걸어 로컬(파일 존재)만 green이었다. 링크 게이트를 "git 추적 기준"으로 격상해 부류째 봉인했고, 이후 structure + browser(러너 chrome, 26검사) 둘 다 **첫 GREEN**. 교훈: 게이트는 로컬 통과가 아니라 러너 통과가 완료 정의다.
105+
### 소유자 전용 TODO (계정/승인이 필요해서 대신 못 하는 것, 2026-07-12 정리)
106+
107+
1. **npm 퍼블리시**: `pyproc` 이름이 비어 있다(2026-07-11 확인). 소유자 npm 계정으로 `npm login` 후 루트에서 `npm publish`. 이후 외부 소비가 `npm install pyproc` 한 줄(files 배열 준비 완료). 퍼블리시 개시 시점에 릴리즈 절차(docs/operations/release.md)에 publish 단계를 추가한다.
108+
2. **라이브 데모 연결**: Cloudflare Pages(권장) 또는 Netlify에 이 저장소를 연결(빌드 없음, 출력 `/`). 준비물은 저장소에 있음: 루트 `_headers`(COOP/COEP) + 절차 [docs/operations/demoHosting.md](../../docs/operations/demoHosting.md). URL 확보 후 README 2종에 데모 링크 + `gh repo edit --homepage <URL>`.
109+
3. **엔진 독립 이니셔티브 착수 승인**: [mainPlan/engine-independence](../engine-independence/README.md) PRD 검토 후 착수 단계 지시.
110+
111+
### 작업 큐 (다음 세션 재개 지점)
112+
113+
1. **엔진 독립 P0**(소유자 승인 시): engine-independence PRD의 첫 단계부터.
114+
2. **다음 발명 라운드 후보**: 리액티브/%undo 델타 rebase·prune(장수 세션 메모리 부채, 계약 실태 표), Session 리플레이의 스냅샷 베이스 결합(머신 resume 가속), 델타 분기(머신의 git), /home 포함 이미지 v2, SharedKernel+hibernate 결합.
100115
3. **codaro UI 배선 동행**: PyodideEngine이 browserPythonRuntime seam을 실제 사용할 때 나오는 요구를 이 원장에 기록. 그 시점 SHA로 재핀(스냅샷-fork 결함 수정이 들어간 커밋 이후여야 함).
101-
4. **npm 퍼블리시(소유자 계정 필요)**: `pyproc` 이름이 비어 있다. `npm publish`로 선점하면 외부 소비가 `npm install pyproc` 한 줄이 된다. files 배열은 준비 완료.
116+
117+
### 종결 기록
118+
119+
- ~~푸시 후 CI 첫 실행 확인~~ 완료(2026-07-12, 사고 기록): 확인을 방치한 대가로 **전 이력 적색**이었다. 원인은 러너 특이사항이 아니라 로컬-CI 게이트 불일치: docs 2곳이 git 미추적 로컬 규칙 문서를 상대 링크로 걸어 로컬(파일 존재)만 green이었다. 링크 게이트를 "git 추적 기준"으로 격상해 부류째 봉인했고, 이후 structure + browser(러너 chrome, 26검사) 둘 다 **첫 GREEN**. 교훈: 게이트는 로컬 통과가 아니라 러너 통과가 완료 정의다.
120+
- ~~"웹의 uv" 라운드~~ 완료(2026-07-12): 발명 라운드 4 참조.
102121

103122
## 메모리 포인터
104123

0 commit comments

Comments
 (0)