Advance M7 workflows and release operations
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-15 17:43:53 -04:00
parent 17ab961485
commit 7c16b279ae
103 changed files with 8064 additions and 429 deletions

47
docs/web/CI.md Normal file
View 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.

View File

@@ -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.

View 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
View 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).

View 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.

View 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 pages2 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 篡改响应。

View 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."
}
]
}

View File

@@ -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"
}
]
},