Skip to content

Releases: eddmpython/pyproc

v0.0.9 - Engine-agnostic file IO, import auto-load, and output capture

Choose a tag to compare

@eddmpython eddmpython released this 13 Jul 14:25

Three engine-agnostic Runtime capabilities so consumers (dartlab, codaro, xlpod) can drop rt.raw (Pyodide-specific access) and run purely on the Runtime plus its capabilities. Non-breaking: new methods and one new capability only, no existing surface changes.

What's new

  • Runtime.fs - engine-agnostic general file IO, always-on like Runtime.memory: writeFile / readFile (utf8 or binary) / mkdir / mkdirTree / readdir / stat / exists / unlink / rmdir. Mutations bump the execution-boundary counter (reactive guard); reads do not. Persistence (OPFS) stays mountHome - this is the file-op layer over the mounted FS, not a new VFS.
  • Runtime.loadPackagesFromImports(code) - scan a cell's import statements and auto-load the required C-extension wheels (numpy / pandas / ...), no explicit package list.
  • Runtime.setStdout(handler) / setStderr(handler) - capture execution output with a mutable sink for per-cell live streaming; pass null to restore the default.

All three delegate to the EngineContract (this._engine); unsupported engines degrade honestly (no-op or explicit error).

Verification

Real-browser probes (engineContract campaign): fsProbe 10/10 (including a cross-check that Python open() and rt.fs share the same filesystem), outputCaptureProbe 5/5 (mid-cell handler swap stays isolated), loadImportsProbe 3/3 (numpy auto-loaded from an import scan in 995ms). Structure gate 517/0, browser runtime gate 40/40.

Migration notes (swapping raw for rt.*)

These are behavioral differences, not bugs. Check them before swapping call sites:

  1. rt.fs.readFile(path) returns a Uint8Array (binary) by default. For text pass { encoding: "utf8" }. If your raw.FS.readFile calls already passed {encoding:"utf8"}, keep it.
  2. rt.fs.stat(path) returns a normalized minimal shape { size, isDir, isFile, mtimeMs }, not the full emscripten stat (no raw mode bits / uid / gid / atime / ino). Use .isDir in place of FS.isDir(FS.stat(p).mode). mtimeMs is best-effort (may be null).
  3. rt.fs mutations (writeFile/mkdir/mkdirTree/unlink/rmdir) bump the execution-boundary counter (execSeq); raw.FS.* did not. If you use ReactiveController, file writes now count as execution boundaries. This is intended (FS mutations are real state changes), but it changes checkpoint boundary counts versus raw.
  4. rt.setStdout(handler) is a mutable batched sink (fires on newline, Pyodide batched semantics). There is no unbatched / char-level or auto-scoped variant - you set it and reset it (null) per cell yourself.
  5. rt.fs.readdir(path) already filters . and .. - do not filter again.

Not included (out of scope this release)

  1. WASI parity is not verified. These three run on Pyodide today; on non-Pyodide engines they degrade honestly (no-op / error). "Runs unchanged on WASI" is a contract goal, not a measured fact yet. Do not assume an engine swap keeps fs / imports / stdout working until the WASI engine is wired to the contract and measured.
  2. The FS surface is the 8 ops only (writeFile / readFile / mkdir / mkdirTree / readdir / stat / exists / unlink / rmdir). No chmod / rename / symlink / truncate / copyFile / watch. Ask if you need more.
  3. loadPackagesFromImports uses Pyodide's package resolver (its package index), not a general PyPI resolver. Imports of packages outside that index will not auto-load (same as before).
  4. DeviceFs still uses raw.FS internally for device registration (registerDevice / makedev / mkdev) - a separate engine seam, intentionally not migrated. It does not affect your consumption (you use rt.fs); noted for completeness.
  5. No checkpoint adapter was built (per your request - you adapt to ReactiveController via an id / savedSP adapter; the pyproc side is unchanged).
  6. Your call sites (P4) are yours. Nothing in dartlab was touched; the raw-to-capability mapping is in the phasing doc. Success criterion ("zero raw references") completes on your side.

Consuming

Install from npm and pin the exact version:

"pyproc": "0.0.9"

Pyodide stays at v314.0.2 (CPython 3.14).


한국어

소비자(dartlab/codaro/xlpod)가 rt.raw(Pyodide 특정 접근)를 버리고 Runtime + 능력만으로 돌게 하는 엔진-무관 능력 3건. 비브레이킹(새 메서드/능력만, 기존 표면 무변경).

  • Runtime.fs - 엔진-무관 일반 파일 IO, Runtime.memory처럼 상시: writeFile/readFile(utf8·binary)/mkdir/mkdirTree/readdir/stat/exists/unlink/rmdir. 변이는 실행 경계 카운터를 올린다(리액티브 가드), 읽기는 불변. 영속(OPFS)은 mountHome, 이건 그 위 파일-op 레이어(새 VFS 아님).
  • Runtime.loadPackagesFromImports(code) - 셀 import 문을 스캔해 필요한 C확장 휠 자동 로드(명시 목록 없이).
  • Runtime.setStdout(handler) / setStderr(handler) - 가변 싱크로 실행 출력 캡처(셀별 라이브 스트리밍), null로 기본 복원.

전부 EngineContract에 위임, 미지원 엔진은 정직한 degradation(no-op/에러). 실 브라우저 검증: fsProbe 10/10(파이썬 open() <-> rt.fs 동일 FS 교차), outputCaptureProbe 5/5(셀 도중 핸들러 교체 격리), loadImportsProbe 3/3(numpy가 import 스캔만으로 995ms 자동 로드). 구조 게이트 517/0, 브라우저 런타임 게이트 40/40.

이관 주의 (raw -> rt.* 교체 시 동작 차이)

버그가 아니라 동작 차이다. 교체 전에 확인:

  1. rt.fs.readFile(path)기본이 Uint8Array(binary). 문자열은 { encoding: "utf8" }. raw에서 이미 {encoding:"utf8"}를 줬으면 유지.
  2. rt.fs.stat(path)정규화된 최소 형태 { size, isDir, isFile, mtimeMs }(전체 emscripten stat 아님 = mode 비트/uid/gid/atime/ino 없음). FS.isDir(FS.stat(p).mode) 대신 .isDir. mtimeMs는 best-effort(null 가능).
  3. rt.fs 변이(write/mkdir/mkdirTree/unlink/rmdir)는 execSeq를 올린다(raw.FS는 안 올렸다). ReactiveController를 쓰면 파일 쓰기가 실행 경계로 잡힌다(의도된 것: FS 변이는 진짜 상태 변화. 단 체크포인트 경계 카운트가 raw와 달라진다).
  4. rt.setStdout(handler)가변 batched 싱크(개행 경계 flush, Pyodide batched). unbatched/문자 단위나 자동 스코프 변형은 없다 = 셀마다 직접 set/reset(null).
  5. rt.fs.readdir(path)./..를 이미 필터한다(재필터 금지).

이번 릴리즈 범위 밖 (안 한 것)

  1. WASI parity 미검증. 이 3건은 오늘 Pyodide에서 실동 + 비Pyodide는 정직한 degradation(no-op/에러). "WASI에서도 그대로"는 계약 목표지 실측 아님. WASI 엔진이 계약에 배선되고 실측될 때까지 엔진 교체로 fs/imports/stdout이 그대로 돈다고 가정하지 말 것.
  2. FS는 8op만(writeFile/readFile/mkdir/mkdirTree/readdir/stat/exists/unlink/rmdir). chmod/rename/symlink/truncate/copyFile/watch 없음. 필요하면 요청.
  3. loadPackagesFromImports는 Pyodide 리졸버(그 패키지 인덱스) = 일반 PyPI 리졸버 아님. 인덱스 밖 패키지 import는 자동 로드 안 됨(이전과 동일).
  4. DeviceFs는 내부적으로 raw.FS 유지(장치 등록 registerDevice/makedev/mkdev = 별개 엔진 seam, 의도적 비이관). 소비에는 무영향(당신은 rt.fs 사용), 완전성 위해 명시.
  5. 체크포인트 어댑터 미제작(요청대로 = ReactiveController에 id/savedSP 어댑터로 채택, pyproc 측 무변경).
  6. 당신 콜사이트(P4)는 당신 몫. dartlab은 손 안 댔다. raw -> 능력 매핑은 phasing 문서. 성공 기준("raw 0 참조")은 소비자 측에서 완성.

소비: npm에서 정확 버전으로 핀 "pyproc": "0.0.9". Pyodide는 v314.0.2(CPython 3.14) 유지.

v0.0.8 - Vectorized urllib sync-XHR byte restore (~5x)

Choose a tag to compare

@eddmpython eddmpython released this 13 Jul 13:08

Vectorized the urllib sync-XHR response byte restore (x-user-defined text to bytes).

The old restore ran bytes(ord(c) & 0xFF for c in body), a per-character Python loop that cost seconds on MB-scale responses. It now encodes the text to UTF-16LE once (C level) and extracts the low byte of each code unit with numpy. Measured on a 12.8MB body: 1.85s to 0.38s (about 5x), byte-identical across all 256 byte values. Falls back to the old loop when numpy is unavailable.

Non-breaking: no public surface or signature change.


urllib 동기 XHR 응답 바이트 복원(x-user-defined 텍스트 to 바이트)을 벡터화. 옛 복원은 bytes(ord(c) & 0xFF for c in body) 로 문자마다 파이썬을 돌아 MB 응답에서 수 초를 먹었다. UTF-16LE 로 C 레벨 1회 인코딩한 뒤 numpy 로 하위 바이트만 벡터 추출한다. 실측 12.8MB 1.85초 to 0.38초(약 5배), byte-identical(전 256 바이트값 검증), numpy 부재 시 옛 루프로 폴백. 공개 표면·시그니처 변경 없음(비브레이킹).

v0.0.7 - Process OS primitives, self-hosting, and a numerical leap (CPU sharding + GPU)

Choose a tag to compare

@eddmpython eddmpython released this 13 Jul 11:45

Cumulative release of this line since v0.0.6. Everything is additive (no changes or removals to the existing public surface); consumers pin by commit SHA, so nothing breaks. Default Pyodide stays v314.0.2.

browser-os primitives P1-P7 (all browser-measured)

  • KernelElection: leader election via Web Locks + BroadcastChannel RPC + journal failover (the OS survives a tab dying)
  • JobControl: expr & forks the live interactive namespace onto another core (prompt returns immediately; %jobs / %fg / %kill)
  • PyProc flow IPC: pipe / lock / semaphore / shm (SAB ring buffer, 982 MB/s, real blocking read + backpressure)
  • MachineContainer: machine-in-machine (a container kernel in a worker, depth 2, exposed as a Python value)
  • MachineJail: permission jail (a cooperative chokepoint plus the browser's own connect-src CSP wall)
  • DeviceFs fsWorld v2: /proc/<pid>/ctl (write = signal), /dev/fb0 framebuffer, /dev/random, /var/log

engine-independence

  • Self-hosting (npm run fetch:engine): serve the whole distribution from vendor/, boot with boot({indexURL:"/vendor/pyodide/"}) and zero CDN
  • Snapshot pre-manufacture wall mapped (the wall is loadPackage, not dlopen)

Onboarding

  • checkEnvironment(): diagnoses crossOriginIsolated / SAB / JSPI with actionable fixes. Reaching for the process OS without the headers now gives a clear error instead of a cryptic SharedArrayBuffer is not defined

Numerical leap (closing the numpy ~86x gap in two lanes)

  • PyProc.matmul: CPU sharding (row-block distribution), compute-bound near-linear, 2.48x end-to-end, exact
  • GpuCompute / GpuArray / GpuBridge: WebGPU compute with residency handles (upload once, chain matmul / map on the GPU, download once). Large f32 matmul measured 109x vs WASM numpy on a real GPU; Runtime.enableGpu() drives it from Python (pyprocGpu.matmul on numpy arrays, 92x). f32 only (WGSL has no f64); needs a windowed browser with a GPU (headless SKIPs)

New public surface (all additive)

checkEnvironment, SocketBridge, MachineContainer, JobControl, KernelElection, MachineJail, GpuCompute, GpuArray, GpuBridge + PyProc.matmul / exec / repl / pipe / lock / semaphore / shm + DeviceFs.track + Runtime.enableSocketBridge / enableGpu

Gates

Structure 455 + core browser 40/40 + wasiGate 10/10 + examples 5/5 + real-GPU probes all green.

Chromium / Edge only (JSPI + SharedArrayBuffer + crossOriginIsolated). The process OS and sockets need COOP/COEP headers; the GPU lane needs a windowed browser with a GPU.


한국어

v0.0.6 이후 이 라인의 누적 릴리즈. 전부 additive(기존 공개 표면 시그니처 변경·제거 없음), 소비자는 SHA 핀이라 즉시 깨지지 않는다. 기본 Pyodide v314.0.2 불변.

browser-os 근본 프리미티브 P1~P7(전부 브라우저 실측): KernelElection(Web Locks 선출 + 저널 failover = 탭 죽음 생존), JobControl(expr &로 살아있는 네임스페이스 fork), PyProc 흐름 IPC(pipe/lock/semaphore/shm, 982MB/s), MachineContainer(머신 안 머신), MachineJail(권한 감옥 = 협조 + CSP 벽), DeviceFs fsWorld v2(/proc//ctl, /dev/fb0, /dev/random, /var/log).

engine-independence: 자가 호스팅(npm run fetch:engine + boot({indexURL:"/vendor/pyodide/"})로 CDN 0), 스냅샷 사전 제조 벽 좌표 실측.

소비자 온보딩: checkEnvironment()가 crossOriginIsolated/SAB/JSPI 진단 + 실행 가능한 조치(암호 실패 대신 친절한 에러).

수치 성능 도약(numpy 86배를 두 레인으로): PyProc.matmul(CPU 샤딩, 종단 2.48배), GpuCompute/GpuArray/GpuBridge(WebGPU 잔류 핸들, f32 대규모 matmul 실 GPU 109배, Runtime.enableGpu()로 파이썬 numpy 직결 92배. f32 한정, 창 있는 브라우저 + GPU 필요).

Chromium/Edge 전용. 프로세스 OS·소켓은 COOP/COEP 헤더, GPU 레인은 창 있는 브라우저 + GPU 필요.

v0.0.6 - 브라우저 OS 프리미티브 + 정확성 라운드

Choose a tag to compare

@eddmpython eddmpython released this 12 Jul 12:50

v0.0.5 이후 발명 라운드 5~10과 외부 리뷰 대응을 묶은 릴리즈. 브레이킹 1건(.pymachine 포맷 v2). 공개 API 표면은 추가만.

브라우저 OS 프리미티브

  • 진짜 OS 표면: DeviceFs(모든 것은 파일), Init(rc.local/cron), %pip, requests 배선.
  • forkLive = fork(2): 살아있는 프로세스의 변수·배열·계산 결과를 자식으로 복제한다(델타 수확 43.6ms, 적용 1.4ms, 주소공간 독립).
  • 시그널 표: PyProc.signal(pid, signum) + SIGINT/SIGTERM/SIGUSR1/SIGUSR2. SAB 채널에 번호를 쓰면 CPython eval 루프가 그 번호의 파이썬 핸들러를 부른다.
  • 체크포인트 나무: reactive.parents + tree(). 분기 노드 스위치 시 형제 분기 델타를 집어 힙이 깨지던 결함을 부모 체인 walk로 수정했다.
  • MachineJournal(WAL): 유휴 자동 커밋으로 강제종료 후에도 마지막 커밋으로 부활한다(8.8MB, 2330ms). churn 바닥을 규명해(no-op 문장도 90106페이지를 더럽히고 그 집합이 9798% 고정) 커밋 단위를 문장이 아닌 유휴로 확정했다.

셀프호스팅

/home에서 FastAPI + sqlite + HTML을 개발해 서빙한다. GET p50 2.1ms, 재부팅 후 재서빙 3.4s(코드·DB 디스크 생존), 수정 반영 7ms. 가상 오리진 충실화로 요청 헤더 전달, 바이너리 바디, 커널 클라이언트 라우팅, 504 타임아웃, COI 헤더를 넣어 서빙된 iframe의 fetch가 커널에 20ms로 도달한다.

정확성 (외부 리뷰 수용)

RPC reqId 상관 + 워커 사망 시 대기 요청 전량 reject, ASGI 동시 요청 격리, 저널 HEAD/PREV 2세대 복구, 복제 고유성 random 재시드, 부팅 직렬화, 성장 세션 fork, PyProxy destroy 계약.

구조

EngineContract seam으로 엔진 접점 8파일 40지점을 pyodideEngine.js 한 파일 뒤로 격리했다(동작 무변경).

브레이킹

.pymachine 포맷 v2. 봉투 전체(헤더+델타)를 해시 인증해 manifest.setup 변조를 막는다. v1 이미지는 더 이상 로드되지 않는다.

라이선스

MPL-2.0(0.0.5까지는 Apache-2.0). 엔진 Pyodide와 같은 조건이다. 임베드는 자유고, pyproc 자체 파일의 수정분은 소스 공개다.

게이트

구조 298검사 + 브라우저 38검사 GREEN.

v0.0.5 - 파이썬 머신, uv 레인, 가상 오리진, 공유 커널

Choose a tag to compare

@eddmpython eddmpython released this 11 Jul 23:02

v0.0.4 이후 발명 라운드와 감사 후속을 묶은 릴리즈. 브레이킹 없음(공개 표면은 추가만, 세션 저장물은 하위 호환).

파이썬 머신 (browser-os)

  • .pymachine 단일 파일 이미지: SHA-256 무결성 + trust 명시 승인 + cp0 결정성 대조(h0). 13.7MB 파일로 컴퓨터가 2.5초에 부활.
  • 영속 디스크 Runtime.mountHome(/home/web): 파이썬 open() 파일이 커널을 넘어 생존.
  • 수명주기 데모 examples/machine.html: 탭 닫으면 자동 hibernate, 열면 그 자리에서 resume.
  • 오프라인 부팅 boot({coreCacheDir}): 코어 자산 OPFS 캐시.

uv 레인 (환경의 선언·캐시·재현)

  • bootEnv(manifest, dirs): bare 스냅샷 + OPFS 휠로 2차 환경 부팅 1229ms(콜드 5109ms 대비 4.2배).
  • runScript(rt, src): PEP 723(# /// script) 인라인 의존성 자동 설치 + 실행 = 브라우저판 uv run.
  • Runtime.freeze() + boot({lockFileURL}): 환경을 락으로 고정, 해석 0으로 재현(실측 164ms 핀 설치).

Service Worker 계층

  • pyprocSw.js 자산 + VirtualOrigin: 파이썬 ASGI 서버가 진짜 URL로 응답(fetch("/pyproc/api/..."), 왕복 3.4ms = 직접 dispatch와 동일).
  • ?cache=1: CDN 자산 캐시-우선, 2차 부팅 CDN 요청 0(script 경로 포함 = 비행기 모드 부팅).

공유 커널

  • SharedKernel(SharedWorker): 여러 탭이 한 파이썬 상태를 공유, 탭이 닫혀도 커널 생존. SAB 의존 기능(interrupt/fork)은 플랫폼 제약(SharedWorker COI 미지원)으로 제외를 명시.

프로세스 OS

  • PyProc({packages, setup}) 부팅 계약, mapArray TypedArray 샤딩(32MB sort+sum 4워커 5.28배).
  • interrupt(pid)가 SIGINT 미지원 워커에 정직하게 false(무증상 no-op 제거).

결함 수정

  • subprocess 자식 워커가 자가호스팅 설정을 무시하고 CDN으로 새던 누수(Runtime.indexURL 계약).
  • 세션 리플레이 결정성 무대조(엔진 변화 시 조용한 오염) -> 저장물 h0 대조로 명시적 예외.
  • coreCache 감싼 fetch의 재진입 무한 재귀.

게이트/운영

  • 구조 179검사(camelCase 네이밍 가드, git 추적 기준 링크 가드, README 표면 동기화 가드) + 브라우저 26검사.
  • CI 전 이력 적색 사고 수리(로컬-CI 게이트 불일치 봉인) 후 러너 GREEN, 실측 수치 아티팩트 보존.
  • npm 레지스트리 메타데이터(repository/homepage/bugs) 추가.

v0.0.4 - 로컬 parity 승격 5종

Choose a tag to compare

@eddmpython eddmpython released this 11 Jul 14:08

로컬 parity 승격 5종 + 실측 관문 2종. 브라우저 게이트 20검사 green.

  • restoreLive({rehash}): 예외로 더러워진 힙 안전 복원(17.6ms 실측).
  • AsgiServer: FastAPI를 소켓 0으로 커널 안 dispatch(3.4ms).
  • Terminal: 탭 = 파이썬 REPL(+examples/terminal.html), JSPI 블로킹 input.
  • interrupt(pid): SIGINT 협조적 취소(517ms 수렴, respawn 0).
  • saveBase/loadBase: 체크포인트 base의 OPFS 영속(30MB 쓰기 256ms/읽기 46ms).
  • 관문: Pyodide v314에서 fastapi/pydantic/polars/numpy/requests + 대표 라이브러리 17/17 설치·import 실측.
  • 수명주기(taskTimeoutMs/kill/respawn), 이중 해시 soundness(실효 64비트), syscallBridge v1(input/urllib/subprocess) 포함.

브레이킹 없음(공개 표면 추가만, subpath 불변). 자동 검증: npm test(구조 85) + npm run test:browser(런타임 20).

v0.0.3 - 운영 체계 + 레이어 재구조화

Choose a tag to compare

@eddmpython eddmpython released this 11 Jul 14:08

운영 체계 수립 + src 레이어 재구조화.

  • src를 레이어 폴더(runtime / capabilities / processOs)로 재배치, 순환 import 제거. subpath export 불변.
  • 운영 체계: tests/attempts 졸업 게이트, mainPlan 수명주기(_done), docs 운영 트리, CONTRIBUTING(en/ko).
  • restoreLive 실행 경계 계약 명문화 + README 예제 수정.
  • COOP/COEP 실측 서버(npm run serve) 동봉.

v0.0.2 - first consumer 검증 지점

Choose a tag to compare

@eddmpython eddmpython released this 11 Jul 14:08

내부 규칙 문서를 git 추적에서 제외하고 공개 표면을 정리. codaro가 이 커밋 SHA를 핀해 first consumer로 import를 검증한 지점(npm 해석, tsc 타입, Vite 워커 emit 3단계).

v0.0.1 - 초기 스캐폴드

Choose a tag to compare

@eddmpython eddmpython released this 11 Jul 14:08

초기 스캐폴드. 브라우저 파이썬 프로세스 OS의 코어 4모듈(런타임 래퍼, 복원 리액티브, 프로세스 OS, 능력 계약)과 소비자용 타입 선언(index.d.ts).