diff --git a/my-tools/show_blender_web_scheduler.py b/my-tools/show_blender_web_scheduler.py index 2959ab059..928eb4a9c 100755 --- a/my-tools/show_blender_web_scheduler.py +++ b/my-tools/show_blender_web_scheduler.py @@ -8,6 +8,7 @@ BLOCKED decisions. from __future__ import annotations import argparse +import hashlib import json import statistics import re @@ -189,6 +190,7 @@ def fast_path_status(status: dict) -> dict: cache = _read_json(CONTROL_ROOT / "context-cache.json") ledger = _read_json(CONTROL_ROOT / "failure-ledger.json") planner = _read_json(CONTROL_ROOT / "auto-planner.json") + modules = wasm_module_status() return { "batch": { "batchId": latest_batch.get("batchId"), @@ -213,6 +215,43 @@ def fast_path_status(status: dict) -> dict: }, "commandBudget": {"maxCommands": 48, "maxReadCommands": 16}, "verificationPolicy": "incremental-context-only-until-final-audit", + "wasmModules": modules, + } + + +def wasm_module_status() -> dict: + """Report module preload policy and artifact integrity without fetching or rebuilding.""" + manifest_path = ROOT / "web/app/public/wasm-module-manifest.json" + manifest = _read_json(manifest_path) + entries = [] + for entry in manifest.get("modules", []) if isinstance(manifest.get("modules"), list) else []: + resources = entry.get("resources", {}) if isinstance(entry, dict) else {} + variants = {} + for variant, resource in resources.items() if isinstance(resources, dict) else []: + if not isinstance(resource, dict) or not isinstance(resource.get("url"), str): + continue + relative = resource["url"].lstrip("/") + path = ROOT / "web/app/public" / relative + actual = None + if path.is_file(): + actual = hashlib.sha256(path.read_bytes()).hexdigest() + variants[variant] = { + "url": resource["url"], + "bytes": path.stat().st_size if path.is_file() else 0, + "integrity": actual == resource.get("sha256") if actual else False, + } + entries.append({ + "id": entry.get("id"), + "lifecycle": entry.get("lifecycle"), + "preload": entry.get("preload") is True, + "variants": variants, + }) + return { + "manifest": "web/app/public/wasm-module-manifest.json" if manifest else None, + "moduleCount": len(entries), + "preloadCompleted": sum(1 for item in entries if item["lifecycle"] == "complete" and item["preload"]), + "debugOnDemand": sum(1 for item in entries if item["lifecycle"] == "debug" and not item["preload"]), + "entries": entries, } @@ -242,6 +281,7 @@ def format_progress(progress: dict, fast_path: dict | None = None) -> list[str]: f" context cache: {context_cache.get('status', 'MISSING')} task={context_cache.get('task') or 'none'} inputs={context_cache.get('inputCount', 0)}; failure ledger entries={ledger.get('entryCount', 0)} hydratedLogs={ledger.get('hydratedLogs', 0)}", f" command budget: max={fast_path.get('commandBudget', {}).get('maxCommands', 48)}, read/exploration max={fast_path.get('commandBudget', {}).get('maxReadCommands', 16)}; verification={fast_path.get('verificationPolicy', 'n/a')}", f" auto planner: {planner.get('status', 'MISSING')} pending={planner.get('pending', 'n/a')} next={planner.get('nextAction') or 'none'}", + f" wasm modules: manifest={fast_path.get('wasmModules', {}).get('manifest') or 'missing'} completed-preload={fast_path.get('wasmModules', {}).get('preloadCompleted', 0)} debug-on-demand={fast_path.get('wasmModules', {}).get('debugOnDemand', 0)}", ]