Advance M7 workflows and release operations
This commit is contained in:
47
docs/web/CI.md
Normal file
47
docs/web/CI.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# M6 CI contract
|
||||
|
||||
The repository remote is Gitea, so the authoritative workflow is
|
||||
`.gitea/workflows/m6-ci.yml`. `main` is the protected release ref. Pull requests and ordinary
|
||||
pushes run quick and Chromium lanes; the release lane runs only for a `main` push or an explicit
|
||||
`workflow_dispatch`.
|
||||
|
||||
## Pinned runner matrix
|
||||
|
||||
| Component | Pinned value | Lane |
|
||||
| --- | --- | --- |
|
||||
| Runner | self-hosted Debian 13 x86_64, labels `blender-web` / `blender-web-release` | all |
|
||||
| Node.js | 20.19.2 | all |
|
||||
| npm | lockfile-compatible npm 9 | all |
|
||||
| Playwright | 1.62.1 | Chromium, release |
|
||||
| Chromium | Playwright Chromium 149.0.7827.55 or `CHROME_PATH` override | Chromium, release |
|
||||
| Blender | official Blender 5.2.0 LTS with USD enabled, configured by `BLENDER_BIN` | release |
|
||||
| Emscripten | 3.1.69 | release |
|
||||
|
||||
`BLENDER_ARCHIVE_SHA256` must identify the official Blender archive used by acceptance. VDB jobs
|
||||
use `VDB_RESOURCE_ROOT`; when it is absent, tools resolve `resource-library/blender-web-vdb`
|
||||
under the runner home directory.
|
||||
|
||||
## Lane contents
|
||||
|
||||
`npm --prefix web run ci:quick` installs from `package-lock.json`, then runs typecheck, lint, Node
|
||||
tests, status consistency and release-evidence schema checks.
|
||||
|
||||
`npm --prefix web run ci:chromium` runs the P0 user loop (including main-thread and Offscreen
|
||||
viewports), release browser smoke, network interruption, device loss, OOM, real OPFS quota,
|
||||
malicious blend and archive gates. Every browser command receives a newly allocated loopback port;
|
||||
Playwright owns and cleans its server process.
|
||||
|
||||
`npm --prefix web run ci:release` runs full E2E, the complete performance and VDB matrices, V1
|
||||
acceptance, reproducible offline packaging, archive cold boot, and binary/source independent
|
||||
verification.
|
||||
|
||||
Each lane writes `release/ci-reports/<lane>.json`, a sidecar SHA-256 and one immutable log per
|
||||
command. Reports use `docs/status/ci-lane-report.schema.json` and bind the commit, lockfile, parity
|
||||
ledger, engine manifest, SBOM, available archives and every command log. A failed subcommand writes
|
||||
an atomic `FAILED` report before the lane exits nonzero; it cannot reuse an older READY report.
|
||||
|
||||
The npm cache key is derived from `web/package-lock.json`. The Emscripten cache additionally binds
|
||||
version 3.1.69 and `tools/web/emscripten-env.sh`. Release evidence, reports and archives are never
|
||||
cache inputs. Artifact retention is fixed at 7 days for quick, 14 days for Chromium and 30 days for
|
||||
release; the report checker runs before upload.
|
||||
|
||||
@@ -13,6 +13,173 @@ Production uses HTTPS. Loopback development may use `http://127.0.0.1`; `file://
|
||||
Runtime assets are same-origin. A reverse proxy must not strip the isolation, range, cache, MIME or
|
||||
ETag headers.
|
||||
|
||||
## Empty-directory install runbook
|
||||
|
||||
The release delivery contains `blender-web-offline.tar.gz`,
|
||||
`blender-web-corresponding-source.tar.gz` and `SHA256SUMS.txt`. Install as an unprivileged deployment
|
||||
user on a Linux host with GNU `tar`, `sha256sum`, `awk`, `mktemp`, `ln` and `mv`. Start with an empty
|
||||
install root; do not extract directly over a running release.
|
||||
|
||||
Set explicit paths. `BLENDER_WEB_INSTALL_ROOT` must be a new or dedicated directory, never `/`, a
|
||||
home directory or a shared workspace.
|
||||
|
||||
```bash
|
||||
BLENDER_WEB_DELIVERY=/absolute/path/to/delivery
|
||||
BLENDER_WEB_INSTALL_ROOT=/srv/blender-web
|
||||
BLENDER_WEB_ARCHIVE="$BLENDER_WEB_DELIVERY/blender-web-offline.tar.gz"
|
||||
BLENDER_WEB_SUMS="$BLENDER_WEB_DELIVERY/SHA256SUMS.txt"
|
||||
|
||||
test -f "$BLENDER_WEB_ARCHIVE"
|
||||
test -f "$BLENDER_WEB_SUMS"
|
||||
mkdir -p "$BLENDER_WEB_INSTALL_ROOT/releases"
|
||||
|
||||
BLENDER_WEB_EXPECTED_SHA256="$(awk '$2 == "blender-web-offline.tar.gz" { print $1 }' "$BLENDER_WEB_SUMS")"
|
||||
BLENDER_WEB_ACTUAL_SHA256="$(sha256sum "$BLENDER_WEB_ARCHIVE" | awk '{ print $1 }')"
|
||||
test -n "$BLENDER_WEB_EXPECTED_SHA256"
|
||||
test "$BLENDER_WEB_ACTUAL_SHA256" = "$BLENDER_WEB_EXPECTED_SHA256"
|
||||
|
||||
tar -tzf "$BLENDER_WEB_ARCHIVE"
|
||||
BLENDER_WEB_STAGE="$(mktemp -d "$BLENDER_WEB_INSTALL_ROOT/.install.XXXXXX")"
|
||||
tar --no-same-owner --no-same-permissions -xzf "$BLENDER_WEB_ARCHIVE" -C "$BLENDER_WEB_STAGE"
|
||||
test -f "$BLENDER_WEB_STAGE/blender-web-offline/app/index.html"
|
||||
test -f "$BLENDER_WEB_STAGE/blender-web-offline/deployment-contract.json"
|
||||
|
||||
BLENDER_WEB_RELEASE_DIR="$BLENDER_WEB_INSTALL_ROOT/releases/$BLENDER_WEB_ACTUAL_SHA256"
|
||||
test ! -e "$BLENDER_WEB_RELEASE_DIR"
|
||||
mv "$BLENDER_WEB_STAGE/blender-web-offline" "$BLENDER_WEB_RELEASE_DIR"
|
||||
rmdir "$BLENDER_WEB_STAGE"
|
||||
ln -s "releases/$BLENDER_WEB_ACTUAL_SHA256" "$BLENDER_WEB_INSTALL_ROOT/.current.next"
|
||||
mv -Tf "$BLENDER_WEB_INSTALL_ROOT/.current.next" "$BLENDER_WEB_INSTALL_ROOT/current"
|
||||
test -f "$BLENDER_WEB_INSTALL_ROOT/current/app/index.html"
|
||||
```
|
||||
|
||||
Before extraction, operators must inspect the `tar -tzf` output and reject absolute paths, `..`
|
||||
components, links and entries outside `blender-web-offline/`. The automated M6 gate performs these
|
||||
checks before it extracts anything. The content SHA-256 is the immutable release directory name;
|
||||
`current` is the only mutable pointer. The server document root is
|
||||
`$BLENDER_WEB_INSTALL_ROOT/current/app`, while `deployment-contract.json` remains beside `app/`.
|
||||
|
||||
## Transport preflight
|
||||
|
||||
Choose and record one origin before starting the server:
|
||||
|
||||
- Production: `https://<public-host>[:port]`. TLS terminates at the compatible server or reverse
|
||||
proxy. Plain HTTP on a public DNS name, LAN address, `0.0.0.0` or container hostname is rejected.
|
||||
- Local verification only: `http://127.0.0.1:<port>`. Do not publish the loopback-only server by
|
||||
changing its bind address.
|
||||
- `file://`, origins with embedded credentials and deployments below a URL path prefix are
|
||||
unsupported. The application and every runtime asset must share one origin.
|
||||
|
||||
An HTTPS reverse proxy must forward requests to the document root without rewriting asset paths and
|
||||
must preserve every header in `responseHeaders.allResponses`. A loopback check is not evidence that a
|
||||
public plain-HTTP deployment is supported.
|
||||
|
||||
After the server starts, replace `BLENDER_WEB_ORIGIN` with the recorded HTTPS or loopback origin and
|
||||
run:
|
||||
|
||||
```bash
|
||||
curl --fail --silent --show-error --head "$BLENDER_WEB_ORIGIN/"
|
||||
curl --fail --silent --show-error --head "$BLENDER_WEB_ORIGIN/engine-manifest.json"
|
||||
curl --fail --silent --show-error \
|
||||
--header 'Range: bytes=0-15' \
|
||||
"$BLENDER_WEB_ORIGIN/vendor/blender/single/web_engine.wasm" >/dev/null
|
||||
```
|
||||
|
||||
The first two responses must contain the three isolation headers, the contract cache policy, a
|
||||
strong ETag and the exact MIME type. The range request must return `206` with `Accept-Ranges`,
|
||||
`Content-Range`, `Content-Length` and the same strong ETag validator. Open the recorded origin in a
|
||||
new Chromium profile and require `window.isSecureContext === true`,
|
||||
`window.crossOriginIsolated === true`, the manifest to be verified and the selected engine to be
|
||||
ready before directing users to the release.
|
||||
|
||||
## Upgrade runbook
|
||||
|
||||
Keep the public origin unchanged. IndexedDB and OPFS are origin-bound; changing scheme, host or port
|
||||
creates a different storage boundary and is not an upgrade. Do not clear site data, browser profiles,
|
||||
OPFS, IndexedDB or the old release directory during an upgrade.
|
||||
|
||||
1. Announce a maintenance window. Require active edits and saves to finish, then have users close or
|
||||
reload existing tabs. A document and all of its content-hashed Workers must come from one release.
|
||||
2. Install the new archive into a new content SHA-256 release directory by repeating the validation
|
||||
and staging steps above, but do not change `current` yet.
|
||||
3. Compare `current/release-metadata.json` with the new `release-metadata.json`. Record the old and new
|
||||
product versions, engine release IDs, IndexedDB schema versions, OPFS project manifest versions,
|
||||
entry assets and Worker assets. Reject a lower storage schema during upgrade. An OPFS schema change
|
||||
requires an explicitly documented migration and recovery test; V1 currently declares schema `1`.
|
||||
4. Start the new directory on a separate loopback-only validation port. Run the header, MIME, range,
|
||||
manifest hash and browser cold-start gates before changing public traffic.
|
||||
5. Export or download a backup of each critical project before the first forward-only storage
|
||||
migration. The current IndexedDB schema is `6`; `onupgradeneeded` applies migrations atomically when
|
||||
the new StorageWorker starts. A failed transaction must leave the old database version intact.
|
||||
6. Stop admitting new sessions, then create `.current.next` pointing to the complete new release and
|
||||
atomically rename it over `current`. Never switch `app/`, `engine-manifest.json`, stable engine files
|
||||
or Worker files separately.
|
||||
7. Revalidate `/`, `/engine-manifest.json`, one engine range, `window.isSecureContext`,
|
||||
`window.crossOriginIsolated`, the selected engine and Storage schema. Reopen a saved OPFS project,
|
||||
compare its revision and blend SHA-256, perform one edit/save, and only then end maintenance.
|
||||
|
||||
HTML, `engine-manifest.json` and stable engine URLs remain `no-cache`; a release switch therefore
|
||||
revalidates them. Content-hashed entry and Worker URLs remain immutable. A manifest `releaseId`
|
||||
mismatch requires a full refresh and must not initialize an engine or open a project. If any preflight
|
||||
or post-switch check fails, leave or restore `current` to the old complete release; do not delete the
|
||||
new directory until diagnostics are captured, and do not delete the old directory until the rollback
|
||||
window ends.
|
||||
|
||||
## Rollback runbook
|
||||
|
||||
Rollback changes only the `current` app/engine release pointer. It never deletes or rewrites browser
|
||||
site data. Keep the public origin unchanged and retain both release directories plus the pre-upgrade
|
||||
project exports throughout the rollback window.
|
||||
|
||||
1. Stop new sessions and require active saves to finish. Record the current project revision and blend
|
||||
SHA-256, the active `current` target, and both releases' `release-metadata.json` files.
|
||||
2. Compare the browser's already-opened IndexedDB schema and OPFS project manifest schema with the
|
||||
target old release. A target with the same IndexedDB schema and the same OPFS schema is directly
|
||||
readable. Re-run its loopback cold-start and project-reopen checks before switching.
|
||||
3. If the browser database has migrated above the old release's IndexedDB schema, in-place rollback is
|
||||
`BLOCKED`: opening the older database version would fail. Do not delete or recreate the database.
|
||||
Deploy a forward-compatible repair release that retains the current storage schema, or restore the
|
||||
pre-upgrade project export under a separate recovery origin/profile supported by the old release.
|
||||
4. If the OPFS project manifest schema differs, in-place rollback is also `BLOCKED` unless that exact
|
||||
reverse reader has its own tested migration. V1 does not claim reverse OPFS migrations.
|
||||
5. For a compatible target, create `.current.next` pointing to the complete old content-hash directory
|
||||
and atomically rename it over `current`. Never copy individual HTML, manifest, engine or Worker files.
|
||||
6. Revalidate transport, cache, manifest and engine identity. Reopen the same project and require its
|
||||
revision and blend SHA-256 to match the pre-rollback values before allowing one edit/save.
|
||||
|
||||
If post-switch validation fails, atomically restore `current` to the newer release. Capture diagnostics
|
||||
before removing a failed target. Browser data, the newer release and project exports remain untouched.
|
||||
|
||||
## Failure diagnostics
|
||||
|
||||
The machine-readable source is `operations-diagnostics.json`. Capture the active release metadata,
|
||||
origin, failing URL, response headers, stable error code and project revision before recovery.
|
||||
|
||||
| Domain | Primary signal | Confirm | Recovery and data boundary |
|
||||
| --- | --- | --- | --- |
|
||||
| MIME | `DEPLOYMENT_MIME_MISMATCH` or streaming/module refusal | HEAD the URL; compare `Content-Type` with the contract | Fix the MIME map and repeat HEAD. Do not clear project storage. |
|
||||
| Range | `DEPLOYMENT_RANGE_INVALID`, `416`, failed resume | Request `bytes=0-15`; inspect `206`, range headers, ETag and If-Range | Fix byte ranges; discard only partial bytes whose ETag changed. Keep verified OPFS bytes. |
|
||||
| Isolation | `PLATFORM_CAPABILITY_UNAVAILABLE`, no SharedArrayBuffer | Verify HTTPS or `127.0.0.1`, all three isolation headers and both browser flags | Restore headers/same-origin assets or use the gated single variant. Do not open through a blocked pthread path. |
|
||||
| Hash | `ENGINE_VARIANT_INTEGRITY_FAILED` | Re-fetch no-cache manifest; hash JS/WASM/Worker and compare one `releaseId` | Restore one complete release and reload. No fallback or project open is allowed on mismatch. |
|
||||
| Quota | `STORAGE_QUOTA` or `QuotaExceededError` | Record storage estimate and compare committed revision/SHA-256 | Export the project and remove only selected disposable caches. Never clear all site data. |
|
||||
| Worker | `WORKER_TERMINATED` or Worker load failure | HEAD the hashed Worker; compare MIME/cache/path with release metadata | Reload one release, restart Worker and reopen the verified revision. Reject late results. |
|
||||
| GPU | `GPU_DEVICE_LOST` or a GPU budget code | Record loss reason, limits/resident bytes and unchanged project revision | Release/recreate viewport resources or reduce budget. Never rewrite Main/OPFS for GPU recovery. |
|
||||
|
||||
Run `npm --prefix web run test:operations-diagnostics` to validate all seven entries against the
|
||||
deployment contract and the packaged documentation.
|
||||
|
||||
## Fresh-directory rehearsal
|
||||
|
||||
Before freezing an RC, run `npm --prefix web run test:operations-rehearsal`. It creates one new
|
||||
temporary root and executes delivery checksum/path validation, initial install, loopback HTTP
|
||||
preflight, project seeding, complete-release upgrade, HTTP revalidation, compatible rollback and a
|
||||
final HTTP/project-integrity check in that order. It never uses an existing install directory.
|
||||
|
||||
The machine report is `release/operations-reports/rehearsal.json`. Every record contains the runbook
|
||||
command, exit code, duration and observed output. `READY` requires all records to pass, the same
|
||||
project revision and SHA-256 before/after both switches, both release directories to remain present,
|
||||
and cleanup of the temporary rehearsal root after the report is assembled.
|
||||
|
||||
## Cache policy
|
||||
|
||||
- `/` and `/index.html`: `Cache-Control: no-cache` so a deployment is revalidated.
|
||||
@@ -21,6 +188,11 @@ ETag headers.
|
||||
- `/vendor/blender/*`: `Cache-Control: no-cache`; these stable URLs are verified by the engine manifest.
|
||||
- Other paths: `Cache-Control: no-cache` until a more specific content-addressed rule exists.
|
||||
|
||||
Responses carry a strong content SHA-256 ETag. A matching `If-None-Match` returns `304` with no body;
|
||||
after HTML, manifest or a stable engine asset changes, the old validator returns the new complete response
|
||||
with `200` and a different ETag. Immutable `/assets/*` names must include the Vite content hash and are
|
||||
never reused for different bytes.
|
||||
|
||||
The server must use the MIME map in the JSON contract. In particular, WebAssembly is
|
||||
`application/wasm`, JavaScript is `text/javascript; charset=utf-8`, and NanoVDB is
|
||||
`application/x-nanovdb`.
|
||||
@@ -32,5 +204,9 @@ The server must use the MIME map in the JSON contract. In particular, WebAssembl
|
||||
unsatisfied range returns `416`. An `If-Range` mismatch ignores the range and returns the complete
|
||||
resource with status `200`; partial bytes from a different revision must never be combined.
|
||||
|
||||
Run `node tools/web/check-deployment-contract.mjs` for the static contract gate. Production HTTP
|
||||
behavior is a separate M6 acceptance gate and must exercise an actual server before release.
|
||||
Run `node tools/web/check-deployment-contract.mjs` for the static contract gate and
|
||||
`npm --prefix web run test:deployment-runbook` for the fresh-directory install and transport
|
||||
preflight gate. Run `npm --prefix web run test:upgrade-runbook` for the two-release manifest, cache,
|
||||
storage schema, Worker and atomic-switch gate, and `npm --prefix web run test:rollback-runbook` for
|
||||
compatible and blocked-old-reader rollback paths. Production HTTP behavior is a separate M6
|
||||
acceptance gate and must exercise an actual server before release.
|
||||
|
||||
68
docs/web/KNOWN_LIMITATIONS.md
Normal file
68
docs/web/KNOWN_LIMITATIONS.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# Web Blender Modeler V1 Known Limitations
|
||||
|
||||
These limits apply to `0.1.0-rc.1`. A successful bounded gate does not imply complete Blender 5.2
|
||||
parity or support beyond the declared operation, fixture, resource budget and browser configuration.
|
||||
See [V1_SCOPE.md](V1_SCOPE.md) and [parity-ledger.json](parity-ledger.json) for the authoritative
|
||||
capability boundary.
|
||||
|
||||
## Browser and deployment
|
||||
|
||||
- Chromium is the only V1 browser baseline. Firefox, WebKit, mobile browsers, touch, pen, IME and
|
||||
non-Chromium accessibility compatibility are not release claims.
|
||||
- Production requires HTTPS. `http://127.0.0.1` is allowed for local verification; public plain HTTP
|
||||
and `file://` are unsupported. All app, Worker and engine assets must remain same-origin.
|
||||
- The base viewport uses WebGL2. OffscreenCanvas is a second tested production path. WebGPU is used
|
||||
only for the declared bounded NanoVDB path and is subject to adapter availability and limits.
|
||||
|
||||
## Single-thread and pthread selection
|
||||
|
||||
- `AUTO` selects pthread only when `crossOriginIsolated`, `SharedArrayBuffer` and Worker capability
|
||||
gates are all ready. Otherwise it selects the single-thread variant and reports the pthread gate.
|
||||
- A pthread initialization failure under `AUTO` is cleaned up before one single-thread attempt. A
|
||||
resource request failure outside this path remains an error.
|
||||
- A JS, WASM or pthread Worker SHA-256 mismatch is fatal
|
||||
(`ENGINE_VARIANT_INTEGRITY_FAILED`): there is no integrity fallback and no project is opened.
|
||||
- `PTHREAD_REQUIRED` blocks when its platform gate is not ready. `SINGLE_REQUIRED` never attempts
|
||||
pthread. A mixed app/manifest `releaseId` returns `REFRESH_REQUIRED` before engine initialization or
|
||||
project open; a full reload is required.
|
||||
- Both variants declare 256 initial WebAssembly pages (16 MiB) and a 32,768-page ceiling (2 GiB).
|
||||
This is a manifest ceiling, not a memory reservation or availability guarantee; Chromium, the OS or
|
||||
the device can reject growth earlier.
|
||||
|
||||
## Project storage
|
||||
|
||||
- OPFS and IndexedDB are bound to the exact scheme, host and port. Moving between origins does not
|
||||
migrate projects. Clearing site data or a browser profile deletes locally stored projects and caches.
|
||||
- Chromium and the operating system control quota. V1 reserves no minimum capacity and cannot infer
|
||||
that `navigator.storage.estimate()` free space will remain available through a save.
|
||||
- Atomic save preserves the last committed revision on quota failure, but the new revision is not
|
||||
durable until save succeeds. Export critical projects before upgrades and before deleting caches.
|
||||
- IndexedDB schema migration is forward-only. OPFS project manifest schema has no general reverse
|
||||
migration. An older app is blocked when it cannot read the current schema.
|
||||
|
||||
## CPU, WASM and GPU budgets
|
||||
|
||||
- The RC gates cover 100k, 1M and 10M geometry fixtures; they are not an unlimited scene-size claim.
|
||||
Imports and operations can block earlier when their own vertex, element, transfer, time or memory
|
||||
budgets are exceeded.
|
||||
- A texture asset is limited by protocol to 64 MiB, 16,384 pixels per dimension and 256 requested
|
||||
assets per scene. The actual WebGL `MAX_TEXTURE_SIZE`, address space and GPU memory may be lower.
|
||||
The release gate exercised 4K and 8K textures, not every format or 16K residency combination.
|
||||
- VDB input is bounded to 512 MiB, a NanoVDB bundle to 1 GiB, each chunk to 16 MiB and declared GPU
|
||||
residency to at most 512 MiB. Actual `maxStorageBufferBindingSize` and `maxBufferSize` may be lower.
|
||||
- NanoVDB pages are explicit and LRU-bounded. Automatic viewport page-fault feedback, depth-composed
|
||||
production demand paging and an unbounded multi-grid material system are not included in V1.
|
||||
- GPU device loss or budget failure releases and recreates viewport resources where supported; it does
|
||||
not modify Blender Main or OPFS project bytes. A reduced scene or budget may still be required.
|
||||
|
||||
## Blender feature scope
|
||||
|
||||
Complete PBVH sculpt/paint, arbitrary Geometry Nodes and Shader evaluation, full physics solvers,
|
||||
Cycles, final video encoding, Python/Text autorun, add-ons and native GPU backends are either bounded,
|
||||
server-only or excluded. The exact family slices and stable blockers are in
|
||||
[parity-ledger.json](parity-ledger.json); unsupported data must be preserved or explicitly blocked,
|
||||
not reported as executed.
|
||||
|
||||
Use [DEPLOYMENT.md](DEPLOYMENT.md) for installation and rollback requirements, and
|
||||
[operations-diagnostics.json](operations-diagnostics.json) for stable failure signals and data-safe
|
||||
recovery boundaries.
|
||||
60
docs/web/RELEASE_NOTES.md
Normal file
60
docs/web/RELEASE_NOTES.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Web Blender Modeler V1 0.1.0-rc.1 Release Notes
|
||||
|
||||
Release candidate identity: `web-blender-0.1.0-rc.1`, engine
|
||||
`blender-wasm-0.1.0-rc.1`.
|
||||
|
||||
This candidate is a Chromium-only browser modeler built around a bounded Blender 5.2 Main/WebEngine
|
||||
subset. It is not a browser port of the complete Blender desktop application. The normative product
|
||||
boundary and capability definitions are in [V1_SCOPE.md](V1_SCOPE.md); the machine-readable family
|
||||
status and exact required/blocked slices are in [parity-ledger.json](parity-ledger.json).
|
||||
|
||||
## Verified V1 capabilities
|
||||
|
||||
- Import `.blend`, edit the declared Object, Mesh and Principled material subset, undo, redo, save
|
||||
atomically, restart the Storage and WebEngine Workers, reopen, and export a semantically checked GLB.
|
||||
- Preserve stable object, mesh and material identities across save/reopen and verify both the
|
||||
main-thread WebGL and OffscreenCanvas Worker production viewports with non-empty pixels.
|
||||
- Select integrity-checked single-thread or pthread WebEngine artifacts from one versioned engine
|
||||
manifest. The pthread variant is used only when the isolated platform prerequisites pass.
|
||||
- Store projects, snapshots and bounded assets in origin-bound OPFS/IndexedDB, including tested
|
||||
interruption, Worker restart, quota failure and recovery behavior.
|
||||
- Exercise bounded 100k, 1M and 10M geometry, 4K/8K textures, 600-frame simulation cache and
|
||||
one-million-frame media indexing gates.
|
||||
- Exercise deterministic WASM, OPFS, GPU and NanoVDB out-of-memory recovery, WebGL device loss,
|
||||
network interruption, malformed `.blend` input and malicious archive rejection.
|
||||
- Deliver deterministic offline binary and corresponding-source archives with SHA-256 checksums,
|
||||
SPDX SBOM, licenses, source offer, deployment contract and operations diagnostics.
|
||||
- Reproduce quick, Chromium and release CI lanes with lockfile, source, engine, archive and per-command
|
||||
log hash bindings.
|
||||
|
||||
## Capability boundaries
|
||||
|
||||
`LOCAL_EXACT` and `LOCAL_BOUNDED` mean only the fixtures, operations and budgets declared in
|
||||
[V1_SCOPE.md](V1_SCOPE.md) and [parity-ledger.json](parity-ledger.json). Unsupported operations must
|
||||
remain blocked or preserved; the release does not infer support from a readable Blender data block.
|
||||
|
||||
`SERVER` covers validated request/result boundaries for work such as Cycles rendering, complex physics
|
||||
bakes and final video encoding. This offline candidate does not bundle or configure those services and
|
||||
does not claim local-equivalent execution.
|
||||
|
||||
`EXCLUDED` includes arbitrary Python/Text autorun, add-on installation, native desktop windows and
|
||||
native CUDA, Metal, HIP or OptiX backends. The UI must not report those operations as successful.
|
||||
|
||||
All 12 V1 family release slices are `READY`, while all 12 complete Blender 5.2 family parity states are
|
||||
`BLOCKED`. The exact blocked and excluded slice names remain authoritative in
|
||||
[parity-ledger.json](parity-ledger.json); this release note does not convert them to completed work.
|
||||
|
||||
## Delivery evidence
|
||||
|
||||
Verify `SHA256SUMS.txt`, then verify `RC_MANIFEST.json.sha256` and `RC_MANIFEST.json` before deployment.
|
||||
The manifest binds the RC version, base commit, engine release ID, package lock, ledger, embedded release
|
||||
metadata, SBOM, binary/source archives and operations reports.
|
||||
|
||||
Read [Known limitations](KNOWN_LIMITATIONS.md) before deployment, especially the browser, engine
|
||||
variant, origin-bound storage, quota, WASM and GPU budget boundaries.
|
||||
|
||||
Use [Release recovery](RELEASE_RECOVERY.md) to verify delivery evidence, export project backups and
|
||||
handle pre-switch failures, compatible rollback or blocked schema downgrade.
|
||||
|
||||
Deployment requirements and operator commands are in [DEPLOYMENT.md](DEPLOYMENT.md). Stable error
|
||||
signals and recovery boundaries are in [operations-diagnostics.json](operations-diagnostics.json).
|
||||
105
docs/web/RELEASE_RECOVERY.md
Normal file
105
docs/web/RELEASE_RECOVERY.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Web Blender Modeler V1 Release Recovery
|
||||
|
||||
Use this procedure for `0.1.0-rc.1` upgrades and failed deployments. The complete installation,
|
||||
upgrade and rollback contract remains [DEPLOYMENT.md](DEPLOYMENT.md). Never clear Chromium site data,
|
||||
delete a browser profile, overwrite a release directory or downgrade a storage schema as a recovery
|
||||
shortcut.
|
||||
|
||||
## Verify the delivery
|
||||
|
||||
The delivery directory must contain `blender-web-offline.tar.gz`,
|
||||
`blender-web-corresponding-source.tar.gz`, `SHA256SUMS.txt`, `RC_MANIFEST.json` and
|
||||
`RC_MANIFEST.json.sha256`. Verify bytes before extracting:
|
||||
|
||||
```bash
|
||||
cd /absolute/path/to/delivery
|
||||
sha256sum --check SHA256SUMS.txt
|
||||
sha256sum --check RC_MANIFEST.json.sha256
|
||||
```
|
||||
|
||||
Both commands must report `OK`. Check that the manifest identity is
|
||||
`web-blender-0.1.0-rc.1`, its commit and `engineReleaseId` match the release record, and its binary,
|
||||
source and SBOM hashes match the delivered files. In a corresponding source checkout, independently
|
||||
run:
|
||||
|
||||
```bash
|
||||
npm --prefix web ci --ignore-scripts
|
||||
npm --prefix web run test:rc-manifest
|
||||
npm --prefix web run test:binary-archive
|
||||
npm --prefix web run test:source-archive
|
||||
node tools/web/check-ci-report.mjs release/ci-reports/quick.json \
|
||||
release/ci-reports/chromium.json release/ci-reports/release.json
|
||||
```
|
||||
|
||||
Do not deploy when any command fails or when a report is not `READY` and bound to the manifest commit,
|
||||
lockfile, ledger, engine and archive hashes.
|
||||
|
||||
## Back up projects before upgrade
|
||||
|
||||
1. Record the exact public origin, including scheme, host and port. Upgrades must retain that origin
|
||||
because IndexedDB and OPFS data are origin-bound.
|
||||
2. For every critical project, finish active edits and use the top-bar **Save Project** action. Wait
|
||||
for the committed/saved state and retain the downloaded `blender-web.blend` outside the browser
|
||||
profile and outside the deployment install root.
|
||||
3. Record the project's displayed SceneIR revision and calculate the external backup hash with
|
||||
`sha256sum blender-web.blend`. Reopen the downloaded file with the current release and confirm the
|
||||
expected objects before entering the maintenance window.
|
||||
4. Record `readlink current`, the old binary archive SHA-256 and both old/new
|
||||
`release-metadata.json` files. Keep the old content-hash release directory, new staged directory,
|
||||
external project backups and browser profile through the rollback window.
|
||||
|
||||
A backup is not complete merely because OPFS still contains a project. The external `.blend`, revision,
|
||||
SHA-256, origin and active release identity form the recovery record.
|
||||
|
||||
## Upgrade failure before the switch
|
||||
|
||||
If checksum, archive path, header, MIME, range, engine hash, Worker or cold-start preflight fails before
|
||||
`current` changes, leave `current` on the old release. Remove only the incomplete staging directory
|
||||
after diagnostics are captured. Do not change IndexedDB, OPFS, browser caches or the old release.
|
||||
|
||||
Repeat the delivery checks and the loopback preflight from [DEPLOYMENT.md](DEPLOYMENT.md). A failed
|
||||
candidate remains uninstalled until its binary hash and all preflight evidence pass.
|
||||
|
||||
## Upgrade failure after the switch
|
||||
|
||||
Stop new sessions and require active saves to finish. Capture the active origin, `current` target,
|
||||
both release metadata files, failing URL/headers, error code, project revision and project SHA-256.
|
||||
|
||||
Compare the browser's opened IndexedDB and OPFS schema versions with the old target:
|
||||
|
||||
- If the IndexedDB versions match and the OPFS project manifest versions match, validate the complete
|
||||
old release on a loopback port. Atomically repoint `current` to that old content-hash directory using
|
||||
the rollback commands in [DEPLOYMENT.md](DEPLOYMENT.md). Revalidate transport and engine identity,
|
||||
reopen the same project, and require the recorded revision and blend SHA-256 before allowing an edit.
|
||||
- If the old IndexedDB schema is lower, rollback is `BLOCKED` with
|
||||
`INDEXEDDB_SCHEMA_DOWNGRADE_UNSUPPORTED`. Do not open, delete or recreate the database with the old app.
|
||||
- If the OPFS schema differs without an exact tested reverse reader, rollback is `BLOCKED` with
|
||||
`OPFS_REVERSE_MIGRATION_UNDECLARED`. V1 does not claim a general reverse OPFS migration.
|
||||
|
||||
For a blocked rollback, keep or restore the newer compatible app release. Deploy a forward-compatible
|
||||
repair using the current storage schemas, or start the old release on a separate recovery origin/profile
|
||||
and import an external pre-upgrade `.blend` backup. Never point the old app at newer incompatible site
|
||||
data.
|
||||
|
||||
## Restore and prove project integrity
|
||||
|
||||
After a compatible rollback or repair:
|
||||
|
||||
1. Open the recorded origin in a new Chromium tab and confirm the expected release/engine identity.
|
||||
2. Use **Recover Project** for unchanged compatible origin storage. If that fails or a separate recovery
|
||||
origin is required, use **Open .blend** and select the external backup.
|
||||
3. Confirm the recorded objects and revision, use **Save Project**, then hash the new downloaded
|
||||
`blender-web.blend`. The hash must equal the recorded backup unless a deliberate post-recovery edit
|
||||
was made.
|
||||
4. Keep the failed release, logs, diagnostics and external backup until the recovery record contains the
|
||||
final origin, release ID, revision and SHA-256.
|
||||
|
||||
The automated rehearsal command is:
|
||||
|
||||
```bash
|
||||
npm --prefix web run test:operations-rehearsal
|
||||
```
|
||||
|
||||
It verifies delivery, fresh install, HTTP preflight, project revision/SHA-256 preservation, upgrade,
|
||||
compatible rollback and cleanup in one new temporary root. It does not replace real project exports or
|
||||
authorize a schema downgrade.
|
||||
61
docs/web/engine-manifest-v2.md
Normal file
61
docs/web/engine-manifest-v2.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Engine Manifest Schema v2
|
||||
|
||||
`M6-04A` 冻结双 variant 的数据合同和纯校验器。`M6-04B` 已将生产
|
||||
`web/app/public/engine-manifest.json` 切换到 schema v2,并从两个独立构建目录把资产安装到
|
||||
`/vendor/blender/single/` 与 `/vendor/blender/pthread/`。纯策略选择和双环境实机加载已由
|
||||
`M6-04C-G` 完成;旧 single Worker 入口继续作为 `M6-05` fallback 接线前的完整 Main 兼容路径。
|
||||
|
||||
## 顶层合同
|
||||
|
||||
- `schemaVersion` 固定为 `2`,`protocolVersion` 固定为 `1`,`engine` 固定为
|
||||
`blender-wasm`。
|
||||
- `releaseId` 是整个 manifest 的原子发布身份;single/pthread 及其 JS、WASM、worker 摘要
|
||||
必须作为同一份 manifest 一起切换,调用方不得从另一 release 补资源。
|
||||
- `variants` 必须恰好包含一个 `single` 和一个 `pthread`,不能有第三种或重复 variant。
|
||||
- single 与 pthread 的 JS URL 必须不同,WASM URL 也必须不同,避免通过通用路径隐含构建类型。
|
||||
|
||||
## 资源合同
|
||||
|
||||
每项资源都必须声明 `fileName`、同源根路径 `url` 和小写 64 位十六进制 SHA-256:
|
||||
|
||||
| variant | 必需资源 | 禁止资源 |
|
||||
| --- | --- | --- |
|
||||
| `single` | `resources.js`、`resources.wasm` | `resources.pthreadWorker` |
|
||||
| `pthread` | `resources.js`、`resources.wasm`、`resources.pthreadWorker` | 无 |
|
||||
|
||||
JS 与 pthread worker 文件名必须以 `.js` 结尾,WASM 文件名必须以 `.wasm` 结尾;URL 的最后一段
|
||||
必须与 `fileName` 完全一致。资源 URL 不允许远程 origin、query、fragment、反斜线或路径穿越。
|
||||
`pthreadWorker` 表示 worker 加载角色,不强制是第四个物理文件:当前 Emscripten pthread 输出会
|
||||
让 worker 重新加载同一个 pthread ES module,因此该角色可以与 `resources.js` 使用相同的
|
||||
`fileName`、`url` 和 SHA-256;同一 URL 不允许声明不同摘要。worker 不得引用 single 的 JS。
|
||||
若后续工具链产生独立 worker 文件,则三个字段必须描述该文件。
|
||||
|
||||
## 内存合同
|
||||
|
||||
WASM page 固定为 65,536 bytes。每个 variant 都独立声明 `initialPages`、`maximumPages` 和
|
||||
`shared`:
|
||||
|
||||
- `initialPages` 范围为 256 至 32,768 pages,即 16 MiB 至 2 GiB。
|
||||
- `maximumPages` 不得小于 `initialPages`,且不得超过 32,768 pages(2 GiB)。
|
||||
- single 必须为 `shared=false`;pthread 必须为 `shared=true`。
|
||||
|
||||
权威 TypeScript 类型和无 I/O 校验器位于 `web/protocol/manifest.ts`;结构化正例位于
|
||||
`tests/golden/M6-04A/engine-manifest-v2.json`。校验器只接收输入并返回深层新对象或抛出带稳定
|
||||
`code`/`path` 的 `WebEngineManifestValidationError`,不读取文件、不请求网络,也不选择运行时
|
||||
variant。
|
||||
|
||||
安装命令为 `bash tools/web/install-web-engine-assets.sh`。默认读取 `build_web-single` 和
|
||||
`build_web-pthread`,先在临时目录生成并复核 manifest,再安装四个物理文件。专项命令
|
||||
`npm --prefix web run test:engine-variant-install` 同时覆盖正常复制、同一构建目录拒绝、缺失资产
|
||||
拒绝和失败不改写目标 manifest。
|
||||
|
||||
纯选择器位于 `web/protocol/engine-variant.ts`,策略为 `AUTO`、`SINGLE_REQUIRED` 和
|
||||
`PTHREAD_REQUIRED`。它不读取 DOM、网络、存储或 Worker;调用方必须显式传入已校验 manifest
|
||||
和三项线程能力。`PTHREAD_REQUIRED` 被阻断时不返回 variant。专项命令为
|
||||
`npm --prefix web run test:engine-variant-selection`、`npm --prefix web run test:pthread-engine` 和
|
||||
`npm --prefix web run test:single-thread-unisolated`。
|
||||
|
||||
升级入口为 `bootstrapWebEngineRelease`。旧 HTML 绑定的 expected release 与重新验证后的 manifest
|
||||
不一致时只返回 `REFRESH_REQUIRED`,不会选择或初始化 variant,也不会打开待处理项目。资源
|
||||
SHA-256 不一致统一返回 `ENGINE_VARIANT_INTEGRITY_FAILED`;该错误不可进入 pthread -> single
|
||||
fallback。`npm --prefix web run test:engine-upgrade-safety` 同时覆盖纯策略和真实 Chromium 篡改响应。
|
||||
69
docs/web/operations-diagnostics.json
Normal file
69
docs/web/operations-diagnostics.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"product": "Web Blender Modeler V1",
|
||||
"entries": [
|
||||
{
|
||||
"id": "OPS-MIME",
|
||||
"domain": "mime",
|
||||
"signals": ["DEPLOYMENT_MIME_MISMATCH", "WebAssembly.instantiateStreaming failed", "module script refused"],
|
||||
"checks": ["HEAD the failing URL and record Content-Type", "compare the extension with deployment-contract.json mimeTypes"],
|
||||
"expected": [".wasm application/wasm", ".js text/javascript; charset=utf-8", ".json application/json; charset=utf-8"],
|
||||
"recovery": ["fix the server MIME map", "restart or reload proxy configuration", "repeat HEAD before refreshing Chromium"],
|
||||
"dataSafety": "Do not clear OPFS or IndexedDB; MIME failures occur before project bytes need mutation."
|
||||
},
|
||||
{
|
||||
"id": "OPS-RANGE",
|
||||
"domain": "range",
|
||||
"signals": ["DEPLOYMENT_RANGE_INVALID", "HTTP 416", "partial resource resume failed"],
|
||||
"checks": ["GET the resource with Range: bytes=0-15", "record status, Accept-Ranges, Content-Range, Content-Length and ETag", "repeat with matching and mismatching If-Range"],
|
||||
"expected": ["valid single range returns 206", "invalid or unsatisfied range returns 416", "If-Range mismatch returns full 200"],
|
||||
"recovery": ["enable single byte ranges without compression rewriting", "preserve strong ETag validators", "discard partial bytes from a different ETag before retry"],
|
||||
"dataSafety": "Never append bytes across different ETags; retain the last verified OPFS asset and project commit."
|
||||
},
|
||||
{
|
||||
"id": "OPS-ISOLATION",
|
||||
"domain": "isolation",
|
||||
"signals": ["PLATFORM_CAPABILITY_UNAVAILABLE", "crossOriginIsolated false", "SharedArrayBuffer unavailable"],
|
||||
"checks": ["confirm HTTPS or explicit http://127.0.0.1 loopback", "HEAD the document and error routes", "evaluate window.isSecureContext and window.crossOriginIsolated"],
|
||||
"expected": ["Cross-Origin-Opener-Policy same-origin", "Cross-Origin-Embedder-Policy require-corp", "Cross-Origin-Resource-Policy same-origin"],
|
||||
"recovery": ["restore the three headers on every response", "remove cross-origin runtime assets", "use the single variant only when the pthread capability gate is BLOCKED"],
|
||||
"dataSafety": "Do not open a pending project through a blocked pthread path; correcting headers does not require storage deletion."
|
||||
},
|
||||
{
|
||||
"id": "OPS-HASH",
|
||||
"domain": "hash",
|
||||
"signals": ["ENGINE_VARIANT_INTEGRITY_FAILED", "ENGINE_VARIANT_RESOURCE_HASH_MISMATCH", "manifest rejected"],
|
||||
"checks": ["fetch engine-manifest.json with no-cache", "hash the exact JS, WASM and pthread Worker response bytes", "compare releaseId and every declared SHA-256"],
|
||||
"expected": ["one releaseId binds both variants", "resource SHA-256 equals the manifest", "integrity failure performs no fallback and no project open"],
|
||||
"recovery": ["restore one complete immutable release", "purge only the corrupt HTTP cache entry", "reload the document and revalidate the manifest"],
|
||||
"dataSafety": "Keep the project unopened until all resource hashes pass; never repair integrity by editing project data."
|
||||
},
|
||||
{
|
||||
"id": "OPS-QUOTA",
|
||||
"domain": "quota",
|
||||
"signals": ["STORAGE_QUOTA", "QuotaExceededError", "OPFS staging write failed"],
|
||||
"checks": ["record navigator.storage.estimate usage and quota", "compare the committed project revision and SHA-256", "inspect staging and cache categories without deleting the committed blend"],
|
||||
"expected": ["old committed revision remains readable", "failed staging is not promoted", "Worker restart reports the same committed SHA-256"],
|
||||
"recovery": ["export the committed project", "remove explicitly selected disposable caches", "request persistent storage or free origin quota before retry"],
|
||||
"dataSafety": "Never delete scene.blend, its verified manifest or all site data as a quota recovery shortcut."
|
||||
},
|
||||
{
|
||||
"id": "OPS-WORKER",
|
||||
"domain": "worker",
|
||||
"signals": ["WORKER_TERMINATED", "Worker script load failed", "pending request aborted"],
|
||||
"checks": ["HEAD the content-hashed Worker URL", "confirm JavaScript MIME and immutable cache", "compare Worker assets with release-metadata.json", "capture pending request and resource counters"],
|
||||
"expected": ["all Workers belong to the current release", "terminated Worker publishes no late result", "restart rediscovers the committed project"],
|
||||
"recovery": ["reload to one complete current release", "restart the affected Worker", "reopen the last verified project revision"],
|
||||
"dataSafety": "Do not promote late Worker output or remove OPFS while recovering a Worker."
|
||||
},
|
||||
{
|
||||
"id": "OPS-GPU",
|
||||
"domain": "gpu",
|
||||
"signals": ["GPU_DEVICE_LOST", "GPU_TEXTURE_BUDGET_EXCEEDED", "NANOVDB_GPU_BUDGET_EXCEEDED"],
|
||||
"checks": ["record WebGL context loss or WebGPU device.lost reason", "record adapter limits and requested resident bytes", "confirm CPU, WASM and OPFS project revision did not change"],
|
||||
"expected": ["GPU resources release once", "bounded recovery recreates the viewport", "persistent project SHA-256 remains stable"],
|
||||
"recovery": ["reduce texture or NanoVDB resident budget", "recreate the viewport after device recovery", "reload the current release if bounded recovery fails"],
|
||||
"dataSafety": "GPU recovery may discard viewport resources only; it must not rewrite Main or the committed project."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"dataLicense": "CC0-1.0",
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"name": "blender-web-editor-sbom",
|
||||
"documentNamespace": "https://blender-web.local/spdx/e62e3ed568907b9c7d85050e4f2b6bd874d425cca768a0201c3c57cdd9ad9926",
|
||||
"documentNamespace": "https://blender-web.local/spdx/cf7beefe71131e5d51b45ffa79b7b906b21decc743fc7eafd7cf6791996dea37",
|
||||
"creationInfo": {
|
||||
"created": "1970-01-01T00:00:00Z",
|
||||
"creators": [
|
||||
@@ -3283,7 +3283,7 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-blender-web-editor",
|
||||
"name": "blender-web-editor",
|
||||
"versionInfo": "0.1.0",
|
||||
"versionInfo": "0.1.0-rc.1",
|
||||
"downloadLocation": "NOASSERTION",
|
||||
"filesAnalyzed": false,
|
||||
"licenseConcluded": "NOASSERTION",
|
||||
@@ -3292,7 +3292,7 @@
|
||||
"checksums": [
|
||||
{
|
||||
"algorithm": "SHA256",
|
||||
"checksumValue": "e7b16da68885fe43f32ab7b3eeb2c037b4fd8bf54fe6d5e77049aa51e294b1fb"
|
||||
"checksumValue": "87af8e7d5eb36537541cf941699868a010c1fcea305daa3ce5385453e8fa4557"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user