Skip to content

Commit 38f8f56

Browse files
committed
제품 소비자 장치 파일 게이트 추가
1 parent d0e6f66 commit 38f8f56

6 files changed

Lines changed: 84 additions & 2 deletions

File tree

docs/consuming/capabilityMatrix.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
| Machine journal | idle commit과 WAL로 crashed tab을 마지막 commit으로 되살린다 | `MachineJournal` | Experimental | OPFS, idle commit 정책, pack/prune 운영, 같은 engine/manifest | [journal probe](../../tests/attempts/pythonMachine/journalProbe.html), [large heap journal probe](../../tests/attempts/largeHeapEnvelope/largeHeapProbe.html) | journal probes, product consumer gate | 장기 blob store는 pack/prune 없이 커질 수 있다. 브라우저 저장소 quota의 영향을 받는다 |
2727
| Permission jail | Python code의 net, clipboard, home, worker 권한을 제품 manifest와 CSP로 제한한다 | `MachineJail` | Experimental | 제품 permission manifest, jail context CSP, host allowlist, 권한 UI | [jail probe](../../tests/attempts/pythonMachine/jailProbe.html), [product consumer gate](../../tests/browser/productConsumer.mjs), [trust and permissions](trustPermissions.md) | `npm run test:consumer`, jail probes | 같은 parent window에 있는 code의 모든 side channel을 제거하는 sandbox가 아니다. 강한 격리는 opaque origin 설계가 필요하다 |
2828
| Outbound sockets | Python `socket`, `urllib`, `http.client`이 외부 host:port로 나가게 한다 | `SocketBridge` | Experimental | WS-to-TCP relay, JSPI blocking recv, 제품 relay 운영 | [socket campaign](../../tests/attempts/socketBridge/README.md), [Python socket probe](../../tests/attempts/socketBridge/socketPyProbe.html) | socket bridge probes | inbound socket은 브라우저 보안 벽이다. HTTPS는 relay가 TLS를 처리한다 |
29-
| Device filesystem | 브라우저 기능을 Python file path로 노출한다 | `DeviceFs` | Experimental | 제품이 허용한 device injection, 브라우저 permission, 명확한 파일 경로 정책 | [device FS probe](../../tests/attempts/pythonMachine/deviceFsProbe.html), [machine campaign](../../tests/attempts/pythonMachine/README.md) | device FS probes | 브라우저와 제품 정책이 허용한 장치만 존재한다 |
29+
| Device filesystem | 브라우저 기능을 Python file path로 노출한다 | `DeviceFs` | Experimental | 제품이 허용한 device injection, 브라우저 permission, 명확한 파일 경로 정책 | [product consumer gate](../../tests/browser/productConsumer.mjs), [device FS probe](../../tests/attempts/pythonMachine/deviceFsProbe.html), [machine campaign](../../tests/attempts/pythonMachine/README.md) | `npm run test:consumer`, device FS probes | 브라우저와 제품 정책이 허용한 장치만 존재한다 |
3030
| GPU compute | WebGPU에 f32 array를 올려 GPU-resident linear algebra pipeline을 수행한다 | `GpuCompute`, `GpuArray`, `GpuBridge` | Experimental | WebGPU, windowed browser, 실제 GPU, f32 데이터 경로 | [GPU campaign](../../tests/attempts/gpuCompute/README.md), [GPU tiled probe](../../tests/attempts/gpuCompute/gpuTiledProbe.html), [GPU Python probe](../../tests/attempts/gpuCompute/gpuPythonProbe.html) | GPU probes on real hardware | WGSL은 f64가 없다. headless CI 숫자는 제품 성능 근거가 아니다 |
3131
| Shared kernel and tab survival | 여러 탭이 한 Python state를 공유하고 leader tab 죽음 뒤 follower가 journal에서 이어받는다 | `SharedKernel`, `KernelElection` | Experimental | SharedWorker, Web Locks, OPFS journal, same-origin asset | [shared kernel probe](../../tests/attempts/pythonMachine/sharedKernelProbe.html), [kernel election probe](../../tests/attempts/pythonMachine/kernelElectionProbe.html) | shared kernel probes, kernel election probes | multi-user server가 아니다. 브라우저 lifecycle과 leader/follower semantics를 제품 UX에 반영해야 한다 |
3232
| Machine container and jobs | 브라우저 worker 안에 별도 machine kernel을 띄우고 job control을 제공한다 | `MachineContainer`, `JobControl` | Experimental | `crossOriginIsolated`, worker graph, 제품의 process lifecycle 정책 | [machine container probe](../../tests/attempts/pythonMachine/machineContainerProbe.html), [job control probe](../../tests/attempts/pythonMachine/jobControlProbe.html), [pipe and shm probe](../../tests/attempts/pythonMachine/pipeShmProbe.html) | process OS probes | Linux container나 OS scheduler가 아니다. pyproc의 browser-kernel 격리 모델이다 |

docs/consuming/contract.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ SRI 속성을 직접 걸 수 없으므로, 이 검증은 spawn 전 preflight다.
132132

133133
- `npm test``package.json exports`가 승인된 stable specifier만 노출하는지, 공개 예제가 root API나 subpath export만 소비하는지, `index.d.ts`가 공개 타입 계약을 덮는지 검사한다.
134134
- `npm run test:consumer`는 repo 상대 import 없이 설치된 `node_modules/pyproc`만 노출한 브라우저 앱에서 설치 패키지 계약을 검증한다.
135-
- 같은 consumer gate가 `MachineJournal` commit/recover, `MachineJail` 권한 manifest, signed `.pymachine` export/open, trusted public key와 wrong key 거부, signer fingerprint, `/home/web/resume.py`의 SQLite connection 재개설까지 실행한다.
135+
- 같은 consumer gate가 `DeviceFs` 파일 장치, `MachineJournal` commit/recover, `MachineJail` 권한 manifest, signed `.pymachine` export/open, trusted public key와 wrong key 거부, signer fingerprint, `/home/web/resume.py`의 SQLite connection 재개설까지 실행한다.
136136
- `pyproc/runtime`은 public Runtime wrapper다. 내부 `runtime.js` core는 엔진 래퍼와 `Runtime.fs`만 담당하고, `runtimeApi.js``src/capabilities/runtimeBindings.js` registry를 설치해 `enableReactive` 같은 opt-in capability factory를 제공한다.
137137
- `restoreLive` 실행 경계는 기계 검증 대상이다. 경계를 지키면 즉시 복원(재해싱 0), 위반은 자동 감지되어 재해시 경로로 승격된다. 반환값 `rehashed`로 경로를 확인한다.
138138

@@ -145,6 +145,7 @@ SRI 속성을 직접 걸 수 없으므로, 이 검증은 spawn 전 preflight다.
145145
| package consumer | `pyproc`, `pyproc/assets`, `pyproc/runtime` | `Runtime`, `PyProc`, `getPyProcAssetManifest`, `verifyPyProcAssetIntegrity`, `registerPyProcServiceWorker`, runtime subpath `boot`/`Runtime`, `pyproc-assets` bin | package exports, stable subpath, `index.d.ts`, npm files, CLI graph copy and SRI manifest |
146146
| product consumer - asset path | `pyproc`, `pyproc/assets` | `getPyProcAssetManifest`, `verifyPyProcAssetIntegrity`, `registerPyProcServiceWorker` | `/node_modules/pyproc/` 기준 asset manifest, worker graph SRI, 설치된 `pyprocSw.js` registration, bad worker SRI spawn 전 거부 |
147147
| product consumer - runtime/server | `pyproc` | `boot`, `VirtualOrigin`, Runtime `enableAsgiServer` | 설치 패키지 Runtime boot, Python ASGI app, `fetch("/pyproc/...")` virtual origin 왕복, S3 timing source |
148+
| product consumer - device filesystem | `pyproc` | `DeviceFs`, Runtime `enableDeviceFs` | 설치 패키지 Runtime에서 `/dev/productState``/proc/meminfo`를 Python `open()` 파일 계약으로 읽고 쓴다 |
148149
| product consumer - process OS | `pyproc` | `PyProc` | 설치 패키지 worker graph로 `boot`, `map`, `terminate` 실행, SRI와 ASGI Service Worker prefix 충돌 없음 |
149150
| product consumer - crash resume | `pyproc` | `bootSession`, `MachineJournal`, Runtime `enableJournal` | 설치 패키지 Session reactive boundary를 `MachineJournal.commit()`으로 남기고 새 Session이 `recover()`로 제품 상태를 복구 |
150151
| product consumer - product policy | `pyproc` | `MachineJail` | 제품 permission manifest(`net=false`, `clipboard=false`, `home=true`, `workers=false`)와 Python choke point 집행 |

mainPlan/browser-os-north-star/03-progress-ledger.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,3 +2605,46 @@ NEXT:
26052605
1. 소비자별 배선 상태를 실제 제품 gate freshness evidence와 더 직접 연결한다.
26062606
2. probe 전용 capability 중 `DeviceFs` 또는 `JobControl`처럼 제품 UI로 보일 가치가 큰 행을 다음 승격 후보로 판정한다.
26072607
3. coverage manifest를 benchmark artifact처럼 schema 검증 가능한 산출물로 승격할지 결정한다.
2608+
2609+
## 2026-07-15 - DeviceFs product consumer gate 승격
2610+
2611+
문제:
2612+
2613+
- `DeviceFs`는 OS 목표에서 중요한 "브라우저 능력이 파이썬 파일 경로가 된다" 축인데, 제품 판단 표면은 probe 중심이었다.
2614+
- 능력 매트릭스의 실행 표면에 product consumer gate가 없어 설치 패키지 소비자가 실제로 `Runtime.enableDeviceFs()`를 쓰는 증거가 약했다.
2615+
2616+
완료:
2617+
2618+
- [productConsumerCoverage.mjs](../../tests/browser/productConsumerCoverage.mjs)`product consumer - device filesystem` 행을 추가했다.
2619+
- [productConsumer.mjs](../../tests/browser/productConsumer.mjs)가 설치된 `pyproc`에서 `DeviceFs`를 import하고, `Runtime.enableDeviceFs()``/dev/productState` 사용자 장치와 내장 `/proc/meminfo`를 설치한다.
2620+
- product consumer gate가 Python `open()`으로 `/dev/productState`를 읽고 쓰며, `os.path.exists()``/proc/meminfo`의 실제 heap/execSeq를 확인한다.
2621+
- [contract.md](../../docs/consuming/contract.md)의 설치 패키지 consumer gate coverage 표와 계약 검증 설명에 DeviceFs를 연결했다.
2622+
- [capabilityMatrix.md](../../docs/consuming/capabilityMatrix.md)의 Device filesystem 행을 product consumer gate 실행 표면으로 갱신했다.
2623+
- `npm test``DeviceFs` coverage 행과 실제 product consumer check 문자열을 함께 감시하게 했다.
2624+
2625+
실측:
2626+
2627+
- `npm run test:consumer` GREEN 18/18.
2628+
- DeviceFs installed devices: `/proc/meminfo`, `/dev/clipboard`, `/dev/random`, `/dev/productState`.
2629+
- product consumer coverage manifest 8 rows.
2630+
- 같은 run의 참고 수치: VirtualOrigin 19ms, MachineJournal commit 1471ms/recover 2834ms, signed `.pymachine` export 164ms, trusted open 2264ms.
2631+
2632+
검증:
2633+
2634+
- `git diff --check` PASS.
2635+
- `node --check tests/browser/productConsumerCoverage.mjs` PASS.
2636+
- `node --check tests/browser/productConsumer.mjs` PASS.
2637+
- `node --check tests/run.mjs` PASS.
2638+
- `npm test` PASS, 659 passed, 0 failed.
2639+
- `npm run test:consumer` PASS, GREEN 18/18.
2640+
2641+
판정:
2642+
2643+
- DeviceFs가 probe 전용 근거에서 설치 패키지 기준 제품 소비 증거로 올라갔다.
2644+
- 브라우저 OS 목표의 파일 네임스페이스 축이 더 뾰족해졌다. 제품은 `raw.FS`나 내부 Pyodide API가 아니라 공개 `DeviceFs`와 Python `open()` 계약으로 브라우저 장치를 붙인다.
2645+
2646+
NEXT:
2647+
2648+
1. 다음 probe 전용 승격 후보는 `JobControl`이다. 셸의 `&`, `%fg`, `%kill`이 제품 UI로 보일 때 OS성이 가장 직접적으로 전달된다.
2649+
2. coverage manifest를 benchmark artifact처럼 schema 검증 가능한 산출물로 승격할지 결정한다.
2650+
3. 소비자별 배선 상태를 실제 제품 gate freshness evidence와 더 직접 연결한다.

tests/browser/productConsumer.mjs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ const html = `<!DOCTYPE html>
9494
bootSession,
9595
PyProc,
9696
VirtualOrigin,
97+
DeviceFs,
9798
verifyPyProcAssetIntegrity,
9899
registerPyProcServiceWorker,
99100
openMachine,
@@ -174,6 +175,35 @@ const html = `<!DOCTYPE html>
174175
timings.bootMs = Math.round(performance.now() - t);
175176
check("Runtime boots from installed package", rt.run("sum(range(20))") === 190, timings.bootMs + "ms");
176177
178+
const textDecoder = new TextDecoder();
179+
const productDeviceWrites = [];
180+
const deviceFs = rt.enableDeviceFs({
181+
devices: {
182+
"/dev/productState": {
183+
read: () => JSON.stringify({ mode: "browserOs", value: 41, execSeq: rt.execSeq }),
184+
write: (bytes) => productDeviceWrites.push(textDecoder.decode(bytes)),
185+
},
186+
},
187+
});
188+
const deviceInstall = deviceFs.install();
189+
const deviceOk = rt.run([
190+
"import json, os",
191+
"deviceDoc = json.loads(open('/dev/productState').read())",
192+
"open('/dev/productState', 'w').write('write-from-python')",
193+
"procDoc = json.loads(open('/proc/meminfo').read())",
194+
"deviceOk = deviceDoc['mode'] == 'browserOs' and deviceDoc['value'] == 41",
195+
"deviceOk = deviceOk and os.path.exists('/dev/productState') and os.path.exists('/proc/meminfo')",
196+
"deviceOk = deviceOk and procDoc['heapBytes'] > 0 and procDoc['execSeq'] >= 0",
197+
"deviceOk",
198+
].join("\\n"));
199+
check("DeviceFs exposes installed product devices as Python files",
200+
deviceFs instanceof DeviceFs &&
201+
deviceInstall.installed.includes("/dev/productState") &&
202+
deviceInstall.installed.includes("/proc/meminfo") &&
203+
deviceOk === true &&
204+
productDeviceWrites.join("") === "write-from-python",
205+
deviceInstall.installed.join(","));
206+
177207
rt.run([
178208
"import json",
179209
"async def app(scope, receive, send):",

tests/browser/productConsumerCoverage.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ export const PRODUCT_CONSUMER_COVERAGE = Object.freeze([
3333
publicSurface: Object.freeze(["`boot`", "`VirtualOrigin`", "Runtime `enableAsgiServer`"]),
3434
contract: "설치 패키지 Runtime boot, Python ASGI app, `fetch(\"/pyproc/...\")` virtual origin 왕복, S3 timing source",
3535
}),
36+
Object.freeze({
37+
gate: "product consumer - device filesystem",
38+
specifiers: Object.freeze(["`pyproc`"]),
39+
publicSurface: Object.freeze(["`DeviceFs`", "Runtime `enableDeviceFs`"]),
40+
contract: "설치 패키지 Runtime에서 `/dev/productState`와 `/proc/meminfo`를 Python `open()` 파일 계약으로 읽고 쓴다",
41+
}),
3642
Object.freeze({
3743
gate: "product consumer - process OS",
3844
specifiers: Object.freeze(["`pyproc`"]),

tests/run.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,7 @@ check("설치 패키지 consumer gate coverage가 실제 게이트와 정합", (
593593
"bootSession",
594594
"PyProc",
595595
"VirtualOrigin",
596+
"DeviceFs",
596597
"verifyPyProcAssetIntegrity",
597598
"registerPyProcServiceWorker",
598599
"openMachine",
@@ -620,6 +621,7 @@ check("설치 패키지 consumer gate coverage가 실제 게이트와 정합", (
620621
"installed worker graph SRI verifies",
621622
"installed package SW registers from manifest URL",
622623
"VirtualOrigin fetch reaches Python server from installed package",
624+
"DeviceFs exposes installed product devices as Python files",
623625
"MachineJail enforces installed product permission manifest",
624626
"PyProc worker runs from installed package",
625627
"MachineJournal recovers installed product state after crash boundary",

0 commit comments

Comments
 (0)