Files
workinf_Blender_Wasm/my-tools/wbv2_deferred_verification.py
mes123456 f3e21c6b98
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
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

248 lines
11 KiB
Python

"""Build local workflows while preserving behavioral acceptance for explicit use."""
from __future__ import annotations
import hashlib
import json
import time
import uuid
from wbv2_execution_phase import implementation_only
def verification_timing(runner):
from wbv2_autopilot import read_json
value = read_json(runner.directory / "preferences.json", {}).get(
"verificationTiming", "automatic"
)
if value not in {"automatic", "on_user_use", "after_all_features"}:
raise ValueError("unsupported verificationTiming preference")
return value
def set_verification_timing(runner, value):
from wbv2_autopilot import read_json, write_json
if value not in {"automatic", "on_user_use", "after_all_features"}:
raise ValueError("unsupported verificationTiming preference")
path = runner.directory / "preferences.json"
preferences = read_json(path, {})
preferences.update(schemaVersion=1, verificationTiming=value)
write_json(path, preferences)
def deferred(runner):
return verification_timing(runner) in {"on_user_use", "after_all_features"} and not runner.pending_request
def _digest(runner, relative):
from wbv2_autopilot import contained, sha
path = contained(runner.root, relative)
stat = path.stat()
stamp = (stat.st_dev, stat.st_ino, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns)
cache = runner.__dict__.setdefault("_build_hash_cache", {})
prior = cache.get(relative)
if prior is None or prior[0] != stamp:
prior = (stamp, sha(path))
cache[relative] = prior
return prior[1]
def build_record_valid(runner, record):
from wbv2_autopilot import contained, read_json
if record.get("state") != "built_pending_verification":
return False
try:
path = record["buildReceipt"]
if _digest(runner, path) != record["buildReceiptSha256"]:
return False
receipt = read_json(contained(runner.root, path))
if receipt.get("status") != "built_pending_verification":
return False
inputs = receipt["inputs"]
artifacts = receipt["artifacts"]
if not inputs or not artifacts or not receipt.get("checks"):
return False
return all(_digest(runner, p) == value for p, value in inputs.items()) and all(
_digest(runner, item["path"]) == item["sha256"] for item in artifacts
)
except (OSError, ValueError, KeyError, TypeError):
return False
def work_ready(runner, record):
return runner.receipt_valid(record) or (deferred(runner) and build_record_valid(runner, record))
def dispatch_for(runner):
from wbv2_task_dispatch import TaskDispatch
if not deferred(runner):
return TaskDispatch(runner)
class BuildDependencies:
def __getattr__(self, name):
return getattr(runner, name)
def receipt_valid(self, record):
return work_ready(runner, record)
class BuildDispatch(TaskDispatch):
def dependency_issue(self, job, jobs):
return TaskDispatch(BuildDependencies()).dependency_issue(job, jobs)
return BuildDispatch(runner)
def work_signature(runner, context, jobs):
from wbv2_autopilot import contained
from wbv2_task_dispatch import input_signature
paths = {"tests/golden/WBV2/autopilot/preferences.json"}
for record in runner.state.get("jobs", {}).values():
if record.get("state") == "built_pending_verification":
paths.add(record.get("buildReceipt", ""))
paths.update(record.get("buildArtifacts", []))
stamps = []
for relative in sorted(paths - {""}):
try:
stat = contained(runner.root, relative).stat()
stamps.append((relative, stat.st_size, stat.st_mtime_ns, stat.st_ctime_ns))
except (OSError, ValueError):
stamps.append((relative, None))
return hashlib.sha256(
json.dumps([input_signature(runner, context, jobs), stamps], sort_keys=True).encode()
).hexdigest()
def _build_result(result, implementation_phase=False):
allowed = {"source_build", "typecheck"}
checks = [dict(check) for check in result["checks"] if set(check["kinds"]) & allowed]
if implementation_phase:
checks = [check for check in checks if "typecheck" not in check["kinds"]
or any(str(arg).endswith("tools/web/incremental-web-build.mjs")
for arg in check["argv"])]
for check in checks:
if "typecheck" in check["kinds"]:
check["kinds"] = ["source_build"]
if not checks:
raise ValueError("local workflow has no source or Web build check")
ids = {check["id"] for check in checks}
all_ids = {check["id"] for check in result["checks"]}
for check in checks:
if not set(check["kinds"]) <= allowed:
raise ValueError("build check mixes compilation with deferred behavioral verification")
dependencies = check.get("dependsOn", [])
if not set(dependencies) <= all_ids:
raise ValueError("build check has an unknown dependency")
check["deferredValidationDependencies"] = [name for name in dependencies if name not in ids]
check["dependsOn"] = [name for name in dependencies if name in ids]
return {**result, "checks": checks, "cases": []}
def build_only_attempt(runner, context, job):
from wbv2_autopilot import POLICY, contained, now, sha, write_json
from wbv2_local_execution import execute_checks
previous = runner.state["jobs"].get(job["id"], {})
run_dir = runner.directory / "runs" / f"{job['id']}-{uuid.uuid4().hex[:12]}"
run_dir.mkdir(parents=True)
record = {
"state": "implementing", "executionKind": "build_only",
"attempts": int(previous.get("attempts", 0)) + 1,
"providerFailures": 0, "runDirectory": str(run_dir.relative_to(runner.root)),
}
contract = runner.local_contract_path(job)
record["localContractSha256"] = sha(contained(runner.root, contract))
runner.state["jobs"][job["id"]] = record
protected = runner.protected_hashes(context)
owned = {p: sha(contained(runner.root, p)) for p in job.get("ownedPaths", [])}
record["localInputs"] = {
**owned,
contract: record["localContractSha256"],
context["parentManifest"]: sha(contained(runner.root, context["parentManifest"])),
"my-tools/wbv2_deferred_verification.py": sha(
runner.root / "my-tools/wbv2_deferred_verification.py"
),
"my-tools/wbv2_compilation_contract.py": sha(
runner.root / "my-tools/wbv2_compilation_contract.py"
),
}
write_json(run_dir / "before-worker.json", {"protected": protected, "owned": owned})
runner.checkpoint("implementing", activeJob=job["id"], queue=context,
reason="building; behavioral verification deferred until user use",
nextRetryAt=None, providerFailures=0)
started = time.monotonic()
try:
result = runner.local_result(context, job, run_dir)
build = _build_result(result, implementation_only(runner.root))
runner.validate_result(build, run_dir, compilation_only=True)
paths = sorted(set(result["sourceReferences"] + result["dependencies"] +
job.get("ownedPaths", []) + result["changedFiles"] +
[POLICY, context["card"], context["parentManifest"],
"my-tools/wbv2_deferred_verification.py",
"my-tools/wbv2_compilation_contract.py"]))
inputs = {p: sha(contained(runner.root, p)) for p in paths}
record["localInputs"] = inputs
if any(value is None for value in inputs.values()):
raise ValueError("build input is missing")
env = {
"WBV2_RECEIPT_DIR": str(run_dir / "cases"),
"WBV2_DEPLOYED_ORIGIN": runner.policy["origin"],
"WEB_BUILD_JOBS": str(min(4, runner.policy["maxBuildJobs"])),
"CMAKE_BUILD_PARALLEL_LEVEL": str(min(4, runner.policy["maxBuildJobs"])),
}
if implementation_only(runner.root):
env["WBV2_SKIP_TYPECHECK"] = "1"
checks = execute_checks(runner, build, run_dir, inputs, env)
if protected != runner.protected_hashes(context) or any(
sha(contained(runner.root, p)) != digest for p, digest in inputs.items()
):
raise InterruptedError("build authority or input changed during compilation")
artifacts = []
for p in sorted(set(result["artifacts"])):
digest = sha(contained(runner.root, p))
if digest is None:
raise ValueError(f"compiled artifact missing: {p}")
artifacts.append({"path": p, "sha256": digest})
if not artifacts:
raise ValueError("build has no compiled artifacts")
receipt_path = run_dir / "built-pending-verification.json"
write_json(receipt_path, {
"schemaVersion": 1, "queueTask": context["task"], "job": job["id"],
"parentSha256": context["parentSha256"], "workflow": result["workflow"],
"status": "built_pending_verification", "verificationTiming": verification_timing(runner),
"builtAt": now(), "durationSeconds": time.monotonic() - started,
"sourceUnits": result["sourceUnits"], "routeIds": result["routeIds"],
"sourceReferences": result["sourceReferences"], "inputs": inputs,
"checks": checks, "artifacts": artifacts,
"deferredChecks": [c["id"] for c in result["checks"] if c not in build["checks"]],
"coverageIncrementClaimed": 0,
})
record.update(state="built_pending_verification",
buildReceipt=str(receipt_path.relative_to(runner.root)),
buildReceiptSha256=sha(receipt_path),
buildArtifacts=[a["path"] for a in artifacts],
changedFiles=result["changedFiles"],
reason="compiled; behavioral verification deferred until user use")
runner.checkpoint("ready", activeJob=None)
runner.event("job.built_pending_verification", job=job["id"],
receipt=record["buildReceipt"], seconds=time.monotonic() - started)
return True
except InterruptedError as error:
record.update(state="needs_reconcile", reason=str(error))
runner.checkpoint("needs_reconcile", reason=str(error))
return False
except Exception as error:
changed = protected != runner.protected_hashes(context) or any(
sha(contained(runner.root, p)) != digest for p, digest in owned.items()
)
state = "needs_reconcile" if changed else "blocked"
record.update(state=state, reason=str(error))
runner.checkpoint(state, reason=str(error))
runner.event("job.build_failed", job=job["id"], errorType=type(error).__name__,
seconds=time.monotonic() - started)
return False