Skip to content

Commit 049b659

Browse files
committed
기능: 기본 내구 머신과 자체 엔진 배포를 완성
open()을 OPFS 기반 KernelElection의 기본 진입점으로 바꿨다. run과 세대 commit을 직렬화해 결과가 내구 세대에 들어간 뒤 응답한다. commit 실패는 재실행을 막는 PYPROC_RPC_OUTCOME_UNKNOWN으로 닫는다. 기본 엔진 주소를 /vendor/pyodide/로 옮기고 SRI 검증을 기본 적용했다. pyproc-engine CLI가 core 6개와 lock package 354개를 검증한다. CI와 Pages도 같은 배포판을 준비하며 CDN은 명시 평가 경로만 남겼다. README, 타입, 제품 원장, 설치 및 브라우저 게이트를 새 계약에 맞췄다. 음성 시험: autoCommit 반전과 잘못된 엔진 경로를 각 게이트가 차단했다. 검증: npm test, npm run test:types, npm run test:package 검증: test:browser, test:installed, test:golden, test:web-computer, test:examples
1 parent 86825fc commit 049b659

41 files changed

Lines changed: 575 additions & 305 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ jobs:
3434
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
3535
with:
3636
node-version: 22
37+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
38+
with:
39+
path: vendor/pyodide
40+
key: pyodide-${{ hashFiles('src/runtime/pyodideDistribution.js', 'scripts/assetCatalog.json') }}
41+
- run: npm run fetch:engine
3742
# ubuntu 러너에 Google Chrome이 프리인스톨되어 있다. CI=true라 --no-sandbox가 자동 적용된다.
3843
- run: npm run test:browser
3944
env:
@@ -114,6 +119,11 @@ jobs:
114119
$edge = Get-Item -LiteralPath $env:PYPROC_BROWSER
115120
Write-Host "Microsoft Edge $($edge.VersionInfo.ProductVersion)"
116121
- run: npm ci
122+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
123+
with:
124+
path: vendor/pyodide
125+
key: pyodide-${{ hashFiles('src/runtime/pyodideDistribution.js', 'scripts/assetCatalog.json') }}
126+
- run: npm run fetch:engine
117127
- run: npm test
118128
- run: npm run test:browser
119129
env:

.github/workflows/pages.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,16 @@ jobs:
3333
# 의존성 0이라 여기서 도는 비용이 거의 없다(브라우저 게이트는 ci job이 본다).
3434
- run: npm ci
3535
- run: npm test
36+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
37+
with:
38+
path: vendor/pyodide
39+
key: pyodide-${{ hashFiles('src/runtime/pyodideDistribution.js', 'scripts/assetCatalog.json') }}
40+
- run: npm run fetch:engine
3641
- name: 데모 사이트 조립 (예제 + 라이브러리 + 브랜드 자산 + SW 루트 사본 + 랜딩 루트 승격)
3742
run: |
3843
mkdir -p _site
3944
# assets: 파비콘·헤더 로고가 참조하는 브랜드 마크 정본(랜딩은 assets/, 예제는 ../assets/).
40-
cp -r examples src assets _site/
45+
cp -r examples src assets vendor _site/
4146
cp index.js index.d.ts LICENSE _site/
4247
cp src/capabilities/pyprocSw.js _site/pyprocSw.js
4348
# 랜딩은 examples/index.html이 정본이고 배포 루트로 승격된다(상대 경로가 루트 기준).

README.ko.md

Lines changed: 41 additions & 35 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 43 additions & 37 deletions
Large diffs are not rendered by default.

apps/webComputer/webComputerRuntime.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import {
1010
import { WebComputerContext } from "./webComputerContext.js";
1111
import { createWebComputerDurabilityPolicy } from "./webComputerPersistence.js";
1212

13+
const PRODUCT_ENGINE_INDEX = new URL("./assets/", import.meta.url).href;
14+
1315
function operationControl(lifetimeSignal, timeoutMs) {
1416
const timeoutSignal = AbortSignal.timeout(timeoutMs);
1517
return Object.freeze({
@@ -37,7 +39,7 @@ export class WebComputerRuntime {
3739
return this.context?.computer.inspect().startupMode || "none";
3840
}
3941

40-
async initialize({ deferBoot = false, indexURL } = {}) {
42+
async initialize({ deferBoot = false, indexURL = PRODUCT_ENGINE_INDEX } = {}) {
4143
if (!crossOriginIsolated || typeof SharedArrayBuffer !== "function") {
4244
throw new Error("Web Computer requires cross-origin isolation and SharedArrayBuffer");
4345
}

docs/consuming/capabilityMatrix.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ Rows that carry the Stable label are recorded here with their evidence. A Stable
3333
| Python runtime (`boot`/`run`/`loadPackages`) | before 2026-07-15 | (a) browser gate always on, (b) surface frozen across many 0.0.x releases, (c) long soak with the gate continuously green, (d) documented |
3434
| Deployment asset integrity | before 2026-07-15 | (a) browser plus install-tarball gates, (b) releases elapsed, (c) long soak with the gate continuously green, (d) documented |
3535

36-
Promotion waiting clock: reactive (checkpoint/restore), session (.pymachine), and machineJournal were
36+
Promotion waiting clock: reactive (checkpoint/restore), session (.pymachine), machineJournal, and the
37+
default durable Machine were
3738
re-judged at v0.0.11. Their persistence, portability, and failure contracts changed in that release,
3839
so v0.0.11 is the new reference point for release stability. Calling a lane Stable in the same
3940
release that changes its boundary would erase the meaning of the label.
@@ -55,7 +56,7 @@ A runnable surface is an example a person opens, a browser gate, a probe, or a t
5556
| Outbound sockets | Lets Python `socket`, `urllib`, and `http.client` reach an external host:port | `SocketBridge` (`pyproc/socket`) | Experimental | A WS-to-TCP relay, JSPI blocking recv, and product operation of that relay | [socket campaign](../../tests/attempts/socketBridge/README.md), [Python socket probe](../../tests/attempts/socketBridge/socketPyProbe.html) | socket bridge probes | Inbound sockets are a browser security wall. For HTTPS the relay handles TLS |
5657
| Device filesystem | Exposes browser features as Python file paths | `machine.runtime.enableDeviceFs()` (`DeviceFs`) | Experimental | Explicit device injection, browser permissions, and a clear file-path policy | [installed-package browser gate](../../tests/browser/installedPackageGate.mjs), [device FS probe](../../tests/attempts/pythonMachine/deviceFsProbe.html), [machine campaign](../../tests/attempts/pythonMachine/README.md) | `npm run test:installed`, device FS probes | Only explicitly configured devices allowed by browser policy exist |
5758
| GPU compute | Uploads f32 arrays to WebGPU and runs a GPU-resident linear algebra pipeline | `GpuCompute`, `GpuArray`, `GpuBridge` (`pyproc/gpu`) | Experimental | WebGPU, a windowed browser, a real GPU, and an f32 data path | [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 has no f64. Headless CI numbers are not evidence of product performance |
58-
| Immortal Python Machine | Many tabs share one Python state, and execution continues from the last commit even after the leader tab is force-killed and after every tab closes | `open({ persistent })` (formerly `openPersistentMachine`) -> a `KernelElection` handle; the journal is the durable verb set on `machine.history` (`MachineJournal`) | Experimental | Web Locks, BroadcastChannel, OPFS, and the same origin. Using the SAB-based OS capabilities on top also needs `crossOriginIsolated` and JSPI | [immortal demo](../../examples/immortal.html), [kernel election probe](../../tests/attempts/pythonMachine/kernelElectionProbe.html), [installed-package browser gate](../../tests/browser/installedPackageGate.mjs), [golden workflow](../../tests/browser/goldenWorkflow.mjs) | `npm test` (the [election protocol] section: reject state machine, epoch fencing, outcome records, conditional resend, unsafe-heap refusal, served-cache idempotence, all with negative tests confirmed), `npm run test:installed`, `npm run test:golden` | Not a multi-user server. Recovery is to a completed commit boundary. The [durable RPC state table](contract.md#durable-rpc-state-table-normative) is the retry boundary: a normal follower cannot prove the leader heap portable, so a sent call cut off by failover is outcome-unknown and is not resent |
59+
| Default durable Machine | Opens one named Python Machine across tabs and cold-reopens it after every tab closes | `open()` / `open({ name })` -> `KernelElection`; `run` auto-commits heap, `/home/web`, and forwarded outcome before settling | Beta | Web Locks, BroadcastChannel, OPFS, the verified same-origin engine distribution, and the same origin. SAB-based OS capabilities also need `crossOriginIsolated` and JSPI | [immortal demo](../../examples/immortal.html), [kernel election probe](../../tests/attempts/pythonMachine/kernelElectionProbe.html), [installed-package browser gate](../../tests/browser/installedPackageGate.mjs), [golden workflow](../../tests/browser/goldenWorkflow.mjs) | `npm test` (serialized run/commit, commit-failure outcome-unknown, election fencing and outcome records), `npm run test:installed` (argument-free open plus cold reopen without manual commit), `npm run test:golden` | Not a multi-user server. Recovery is to the last completed command generation. A normal follower cut off mid-command still cannot prove leader-heap portability, so the [durable RPC state table](contract.md#durable-rpc-state-table-normative) closes it as non-retryable outcome-unknown |
5960
| Machine container and jobs | Starts a separate machine kernel inside a browser worker and offers job control | `machine.containers()` -> `MachineContainer`, `machine.jobs()` -> `JobControl` (a process-OS lane: it assumes the same replay pool as `machine.proc`) | Experimental | `crossOriginIsolated`, the worker graph, and an explicit process-lifecycle policy | [browser gate](../../tests/browser/gate.html) (the machine.jobs and machine.containers evidence checks), [installed-package browser gate](../../tests/browser/installedPackageGate.mjs), [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) | `npm run test:browser`, the MachineContainer and JobControl rows of `npm run test:installed`, process OS probes | Not a Linux container and not an OS scheduler. It is pyproc's browser-kernel isolation model |
6061
| Guests on one wire | Two guests on one computer exchange real Ethernet frames instead of merely coexisting | The built-in `network` device of `createWebComputer` (a `MemoryEthernetSwitch`), the `pyprocNet` module inside the Python guest, and the frame law exported from `pyproc/machine` | Experimental | `createWebComputer` with `network` left on, and a guest that speaks frames | [guest network probe](../../tests/webMachine/browser/probes/guestNetworkProbe.html), [packet network probe](../../tests/webMachine/browser/probes/packetNetworkProbe.html) | `npm run test:web-machine` (the guest network probe runs in CI; the Linux crossing is in the local `--v86` lane) | Only ARP and ICMP echo are answered automatically; TCP, UDP, and DNS are the guest's own business. There is no uplink, so guests reach each other and nothing else. Both guests currently share one JS thread, so a frame arrives between `run()` calls rather than during one |
6162
| Non-Pyodide engine seam | Proves the session and time-travel primitives work on non-Pyodide CPython 3.14 WASI | `bootWasi`, `WasiSession` (`pyproc/wasi`) | Research preview | A consumer-provided `wasmURL`, the WASI worker asset, and the JSON value bridge | [WASI browser gate](../../tests/browser/wasiGate.html), [engine port campaign](../../tests/attempts/enginePort/README.md), [WASI REPL probe](../../tests/attempts/enginePort/wasiReplProbe.html) | WASI gate, [dual engine probe](../../tests/webMachine/browser/probes/dualEngineProbe.html) in `npm run test:web-machine:wasi` | Not the production default engine. The value bridge is JSON-centric and C extensions need a static build |

docs/consuming/compatibility.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ This table answers which pyproc surfaces can run in a target environment. The ru
2525

2626
## Engine
2727

28-
- **Pyodide v314.0.2 (CPython 3.14).** Loaded from a CDN by default, self-hostable through `indexURL`. Changing the version is a release-worthy event that requires the full runtime-consistency gate (detail: the runtime-consistency section of [contract.md](contract.md)).
28+
- **Pyodide v314.0.2 (CPython 3.14).** Loaded from the verified same-origin `/vendor/pyodide/` distribution by default. `indexURL` is an explicit distribution override. Changing the version is a release-worthy event that requires the full runtime-consistency gate (detail: the runtime-consistency section of [contract.md](contract.md)).
2929
- The WASI engine (`pyproc/wasi`) is a separate async surface for proving engine independence. Pyodide is the production canon.
3030

3131
## Resource characteristics (for sizing your heap)

0 commit comments

Comments
 (0)