#!/usr/bin/env python3 """Authorize the next source-backed Blender module batch. Each successor is a bounded capability batch. The queue advances only after a single shared Blender Main/WASM/Worker/Chromium chain produces an aggregate receipt for every selected source unit. """ from __future__ import annotations import argparse import hashlib import json import os import re import subprocess from datetime import datetime, timezone from pathlib import Path from wbv2_runtime import CONTROL_ROOT, INDEX, QUEUE, ROOT, read_json, terminal_pointer AUTO_PLANNER_STATUS = CONTROL_ROOT / "auto-planner.json" PREFERRED_BATCHES = [ { "moduleId": "MESH_BEVEL_OPERATOR_BATCH", "title": "verify mesh bevel operator through native Main", "owner": "MESH", "identifiers": ["MESH_OT_bevel"], "rna": [], "fixture": "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend", "test": "web/tests/e2e/wbv2-mesh-topology-production.spec.ts", "production": "tests/golden/{task}/mesh-topology-production.json", "types": [], "batchOperation": "BEVEL", }, { "moduleId": "MESH_INSET_OPERATOR_BATCH", "title": "verify mesh inset operator through native Main", "owner": "MESH", "identifiers": ["MESH_OT_inset"], "rna": [], "fixture": "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend", "test": "web/tests/e2e/wbv2-mesh-topology-production.spec.ts", "production": "tests/golden/{task}/mesh-topology-production.json", "types": [], "batchOperation": "INSET", }, { "moduleId": "MESH_LOOPCUT_OPERATOR_BATCH", "title": "verify mesh loop cut operator through native Main", "owner": "MESH", "identifiers": ["MESH_OT_loopcut"], "rna": [], "fixture": "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend", "test": "web/tests/e2e/wbv2-mesh-topology-production.spec.ts", "production": "tests/golden/{task}/mesh-topology-production.json", "types": [], "batchOperation": "LOOP_CUT", }, { "moduleId": "OBJECT_TRANSFORM_APPLY_OPERATOR_BATCH", "title": "verify object transform apply operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_transform_apply"], "rna": [], "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-transform-apply-batch-production.spec.ts", "production": "tests/golden/{task}/object-transform-apply-production.json", "types": [], }, { "moduleId": "OBJECT_VISIBILITY_CLEAR_OPERATOR_BATCH", "title": "verify object visibility clear operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_hide_view_clear"], "rna": [], "fixture": "tests/files/web/generated/WBV2-P2-002-OBJECT.TRANSFORM_HIERARCHY.blend", "test": "web/tests/e2e/wbv2-object-visibility-clear-batch-production.spec.ts", "production": "tests/golden/{task}/object-visibility-clear-production.json", "types": [], }, { "moduleId": "OBJECT_MOVE_COLLECTION_OPERATOR_BATCH", "title": "verify object move collection operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_move_to_collection"], "rna": [], "fixture": "tests/files/web/generated/WBV2-P24-001-OBJECT_OPERATIONS.MOVE_COLLECTION.blend", "test": "web/tests/e2e/wbv2-object-move-batch-production.spec.ts", "production": "tests/golden/{task}/object-move-production.json", "types": [], }, { "moduleId": "MESH_PRIMITIVE_CUBE_OPERATOR_BATCH", "title": "verify primitive cube operator through native Main", "owner": "MESH", "identifiers": ["MESH_OT_primitive_cube_add"], "rna": [], "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-primitive-cube-batch-production.spec.ts", "production": "tests/golden/{task}/primitive-cube-production.json", "types": [], }, { "moduleId": "OBJECT_PARENT_OPERATOR_BATCH", "title": "verify object parent operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_parent_set"], "rna": [], "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-parent-production.spec.ts", "production": "tests/golden/{task}/object-parent-production.json", "types": [], }, { "moduleId": "OBJECT_COLLECTION_CREATE_OPERATOR_BATCH", "title": "verify collection create operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_collection_add"], "rna": [], "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-collection-create-batch-production.spec.ts", "production": "tests/golden/{task}/collection-create-production.json", "types": [], }, { "moduleId": "OBJECT_VISIBILITY_OPERATOR_BATCH", "title": "verify object visibility operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_hide_view_set"], "rna": [], "fixture": "tests/files/web/generated/WBV2-P2-002-OBJECT.TRANSFORM_HIERARCHY.blend", "test": "web/tests/e2e/wbv2-object-visibility-batch-production.spec.ts", "production": "tests/golden/{task}/object-visibility-production.json", "types": [], }, { "moduleId": "OBJECT_DELETE_OPERATOR_BATCH", "title": "verify object delete operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_delete"], "rna": [], "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-delete-batch-production.spec.ts", "production": "tests/golden/{task}/object-delete-production.json", "types": [], }, { "moduleId": "OBJECT_DUPLICATE_OPERATOR_BATCH", "title": "verify object duplicate operator through native Main", "owner": "OBJECT", "identifiers": ["OBJECT_OT_duplicate"], "rna": [], "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-duplicate-production.spec.ts", "production": "tests/golden/{task}/object-duplicate-production.json", "types": [], }, { "moduleId": "ANIMATION_FRAME_OPERATOR_BATCH", "title": "verify animation frame operator through native Main", "owner": "ANIMATION", "identifiers": ["ANIM_OT_change_frame"], "rna": [], "fixture": "tests/files/web/generated/M16-GAP-00161-action-fcurve-samples.blend", "test": "web/tests/e2e/wbv2-fcurve-sample-production.spec.ts", "production": "tests/golden/{task}/fcurve-sample-production.json", "types": [], }, { "moduleId": "COMPOSITOR_NATIVE_NODE_BATCH", "title": "verify native compositor transform blur alpha reader", "owner": "MEDIA", "identifiers": ["CompositorNodeTransform", "CompositorNodeBlur", "CompositorNodeAlphaOver", "CompositorNodeViewer"], "rna": [], "fixture": "tests/files/web/compositor_node_batch_scene.blend", "test": "web/tests/e2e/compositor-main-reader-production.spec.ts", "production": "tests/golden/{task}/compositor-main-reader-production.json", "types": ["CompositorNodeTransform", "CompositorNodeBlur", "CompositorNodeAlphaOver", "VIEWER"], }, { "moduleId": "COMPOSITOR_NODE_READER_BATCH", "title": "verify compositor node reader batch", "owner": "MEDIA", "identifiers": [], "rna": ["CompositorNodeGlare", "CompositorNodeViewer"], "fixture": "tests/files/web/compositor_scene.blend", "test": "web/tests/e2e/compositor-main-reader-production.spec.ts", "production": "tests/golden/{task}/compositor-main-reader-production.json", "types": ["CompositorNodeGlare", "VIEWER"], }, { "moduleId": "MODIFIER_READER_BATCH", "title": "verify modifier reader batch", "owner": "MODIFIER", "identifiers": ["MOD_datatransfer", "MOD_dynamicpaint", "MOD_explode", "MOD_fluid"], "rna": ["DataTransferModifier", "DynamicPaintModifier", "ExplodeModifier", "FluidModifier", "FluidDomainSettings"], "fixture": "tests/files/web/modifier_reader_batch_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-batch-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["DATA_TRANSFER", "DYNAMIC_PAINT", "EXPLODE", "FLUID"], }, { "moduleId": "MODIFIER_READER_BATCH_TWO", "title": "verify modifier reader batch two", "owner": "MODIFIER", "identifiers": ["MOD_laplaciandeform", "MOD_laplaciansmooth", "MOD_mask", "MOD_shrinkwrap"], "rna": ["LaplacianDeformModifier", "LaplacianSmoothModifier", "MaskModifier", "ShrinkwrapModifier"], "fixture": "tests/files/web/modifier_reader_batch_two_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-batch-two-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["LAPLACIANDEFORM", "LAPLACIANSMOOTH", "MASK", "SHRINKWRAP"], }, { "moduleId": "MODIFIER_READER_BATCH_THREE", "title": "verify modifier reader batch three", "owner": "MODIFIER", "identifiers": ["MOD_remesh", "MOD_skin", "MOD_weighted_normal", "MOD_warp"], "rna": ["RemeshModifier", "SkinModifier", "WeightedNormalModifier", "WarpModifier"], "fixture": "tests/files/web/modifier_reader_batch_three_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-batch-two-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["REMESH", "SKIN", "WEIGHTED_NORMAL", "WARP"], }, { "moduleId": "MODIFIER_READER_BATCH_FOUR", "title": "verify modifier reader batch four", "owner": "MODIFIER", "identifiers": ["MOD_uvproject", "MOD_uvwarp", "MOD_normal_edit", "MOD_volume_to_mesh"], "rna": ["UVProjectModifier", "UVWarpModifier", "NormalEditModifier", "VolumeToMeshModifier"], "fixture": "tests/files/web/modifier_reader_batch_four_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-batch-two-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["UV_PROJECT", "UV_WARP", "NORMAL_EDIT", "VOLUME_TO_MESH"], }, { "moduleId": "MODIFIER_READER_BATCH_FIVE", "title": "verify modifier reader batch five", "owner": "MODIFIER", "identifiers": ["MOD_meshcache", "MOD_meshsequencecache", "MOD_multires", "MOD_ocean"], "rna": ["MeshCacheModifier", "MeshSequenceCacheModifier", "MultiresModifier", "OceanModifier"], "fixture": "tests/files/web/modifier_reader_batch_five_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-batch-two-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["MESH_CACHE", "MESH_SEQUENCE_CACHE", "MULTIRES", "OCEAN"], }, { "moduleId": "MODIFIER_READER_BATCH_SIX", "title": "verify modifier reader batch six", "owner": "MODIFIER", "identifiers": ["MOD_mesh_to_volume", "MOD_particleinstance", "MOD_surface", "MOD_weightvgedit", "MOD_weightvgmix", "MOD_weightvgproximity"], "rna": ["MeshToVolumeModifier", "ParticleInstanceModifier", "SurfaceModifier", "VertexWeightEditModifier", "VertexWeightMixModifier", "VertexWeightProximityModifier"], "fixtures": [ "tests/files/web/generated/M16-GAP-00081-modifier-MESH_TO_VOLUME.blend", "tests/files/web/generated/M16-GAP-00087-modifier-PARTICLE_INSTANCE.blend", "tests/files/web/generated/M16-GAP-00098-modifier-SURFACE.blend", "tests/files/web/generated/M16-GAP-00103-modifier-VERTEX_WEIGHT_EDIT.blend", "tests/files/web/generated/M16-GAP-00104-modifier-VERTEX_WEIGHT_MIX.blend", "tests/files/web/generated/M16-GAP-00105-modifier-VERTEX_WEIGHT_PROXIMITY.blend", ], "test": "web/tests/e2e/wbv2-modifier-reader-multi-fixture-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["MESH_TO_VOLUME", "PARTICLE_INSTANCE", "SURFACE", "VERTEX_WEIGHT_EDIT", "VERTEX_WEIGHT_MIX", "VERTEX_WEIGHT_PROXIMITY"], }, { "moduleId": "MODIFIER_RNA_READER_BATCH", "title": "verify modifier RNA reader batch", "owner": "MODIFIER", "identifiers": ["MOD_lineart", "MOD_particlesystem", "MOD_decimate"], "rna": ["GreasePencilLineartModifier", "ParticleSystemModifier", "DecimateModifier"], "fixtures": [ "tests/files/web/modifier_grease_pencil_scene.blend", "tests/files/web/generated/M16-GAP-00088-modifier-PARTICLE_SYSTEM.blend", "tests/files/web/generated/M16-GAP-00041-modifier-DECIMATE.blend", ], "test": "web/tests/e2e/wbv2-modifier-reader-multi-fixture-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["LINEART", "PARTICLE_SYSTEM", "DECIMATE"], }, { "moduleId": "MODIFIER_VOLUME_DISPLACE_BATCH", "title": "verify volume displace modifier reader", "owner": "MODIFIER", "identifiers": ["MOD_volume_displace"], "rna": ["VolumeDisplaceModifier"], "fixture": "tests/files/web/generated/M16-GAP-00106-modifier-VOLUME_DISPLACE.blend", "test": "web/tests/e2e/wbv2-modifier-reader-multi-fixture-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["VOLUME_DISPLACE"], }, { "moduleId": "SHAPE_KEY_POINT_READER_BATCH", "title": "verify shape key point reader", "owner": "ANIMATION", "identifiers": [], "rna": ["ShapeKeyPoint"], "fixture": "tests/files/web/rigged_shape_scene.blend", "test": "web/tests/e2e/wbv2-shape-key-reader-production.spec.ts", "production": "tests/golden/{task}/shape-key-reader-production.json", "types": [], }, { "moduleId": "MODIFIER_SHAPEKEY_READER_BATCH", "title": "verify shape key modifier reader dependency", "owner": "MODIFIER", "identifiers": ["MOD_shapekey"], "rna": ["ShapeKeyPoint"], "fixture": "tests/files/web/rigged_shape_scene.blend", "test": "web/tests/e2e/wbv2-shape-key-reader-production.spec.ts", "production": "tests/golden/{task}/shape-key-reader-production.json", "types": [], }, { "moduleId": "LINE_STYLE_READER_BATCH", "title": "verify line style reader batch", "owner": "DRAWING", "identifiers": [], "rna": ["FreestyleLineStyle", "LineStyleModifier", "LineStyleAlphaModifier", "LineStyleColorModifier", "LineStyleGeometryModifier", "LineStyleThicknessModifier"], "fixture": "tests/files/web/generated/M16-GAP-00008-datablock-FreestyleLineStyle.blend", "test": "web/tests/e2e/wbv2-line-style-reader-production.spec.ts", "production": "tests/golden/{task}/line-style-reader-production.json", "types": [], }, { "moduleId": "LINE_STYLE_COLLECTION_READER_BATCH", "title": "verify line style collection reader batch", "owner": "DRAWING", "identifiers": [], "rna": ["BlendDataLineStyles", "LineStyleColorModifiers", "LineStyleAlphaModifiers", "LineStyleThicknessModifiers", "LineStyleGeometryModifiers"], "fixture": "tests/files/web/line_style_modifier_batch_scene.blend", "test": "web/tests/e2e/wbv2-line-style-reader-production.spec.ts", "production": "tests/golden/{task}/line-style-reader-production.json", "types": [], }, { "moduleId": "LINE_STYLE_SUBTYPE_READER_BATCH", "title": "verify line style modifier subtype reader batch", "owner": "DRAWING", "identifiers": [], "rna": [ "LineStyleAlphaModifier_AlongStroke", "LineStyleAlphaModifier_CreaseAngle", "LineStyleAlphaModifier_Curvature_3D", "LineStyleAlphaModifier_DistanceFromCamera", "LineStyleAlphaModifier_DistanceFromObject", "LineStyleAlphaModifier_Material", "LineStyleAlphaModifier_Noise", "LineStyleAlphaModifier_Tangent", "LineStyleColorModifier_AlongStroke", "LineStyleColorModifier_CreaseAngle", "LineStyleColorModifier_Curvature_3D", "LineStyleColorModifier_DistanceFromCamera", "LineStyleColorModifier_DistanceFromObject", "LineStyleColorModifier_Material", "LineStyleColorModifier_Noise", "LineStyleColorModifier_Tangent", "LineStyleGeometryModifier_2DOffset", "LineStyleGeometryModifier_2DTransform", "LineStyleGeometryModifier_BackboneStretcher", "LineStyleGeometryModifier_BezierCurve", "LineStyleGeometryModifier_Blueprint", "LineStyleGeometryModifier_GuidingLines", "LineStyleGeometryModifier_PerlinNoise1D", "LineStyleGeometryModifier_PerlinNoise2D", "LineStyleGeometryModifier_Polygonalization", "LineStyleGeometryModifier_Sampling", "LineStyleGeometryModifier_Simplification", "LineStyleGeometryModifier_SinusDisplacement", "LineStyleGeometryModifier_SpatialNoise", "LineStyleGeometryModifier_TipRemover", "LineStyleThicknessModifier_AlongStroke", "LineStyleThicknessModifier_Calligraphy", "LineStyleThicknessModifier_CreaseAngle", "LineStyleThicknessModifier_Curvature_3D", "LineStyleThicknessModifier_DistanceFromCamera", "LineStyleThicknessModifier_DistanceFromObject", "LineStyleThicknessModifier_Material", "LineStyleThicknessModifier_Noise", "LineStyleThicknessModifier_Tangent", ], "fixture": "tests/files/web/line_style_subtype_batch_scene.blend", "test": "web/tests/e2e/wbv2-line-style-reader-production.spec.ts", "production": "tests/golden/{task}/line-style-reader-production.json", "types": [], }, { "moduleId": "FCURVE_MODIFIER_SUBTYPE_READER_BATCH", "title": "verify FCurve modifier subtype reader batch", "owner": "ANIMATION", "identifiers": [], "rna": ["FModifierEnvelope", "FModifierGenerator", "FModifierLimits", "FModifierNoise", "FModifierSmooth", "FModifierStepped"], "fixture": "tests/files/web/fcurve_modifier_subtype_batch_scene.blend", "test": "web/tests/e2e/wbv2-fcurve-modifier-production.spec.ts", "production": "tests/golden/{task}/fcurve-modifier-production.json", "types": [], }, { "moduleId": "MODIFIER_SOURCE_DEPENDENCY_REUSE", "title": "reuse verified modifier receipts for shared source utilities", "owner": "MODIFIER", "identifiers": ["MOD_grease_pencil_util", "MOD_meshcache_mdd", "MOD_meshcache_pc2", "MOD_meshcache_util", "MOD_weightvg_util"], "rna": [], "test": "tools/web/reuse-wbv2-source-dependencies.mjs", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": [], "reuseReceipts": [ {"manifest": "tests/golden/WBV2-P1150-001/manifest.json", "report": "tests/golden/WBV2-P1150-001/module-receipt.json"}, {"manifest": "tests/golden/WBV2-P1151-001/manifest.json", "report": "tests/golden/WBV2-P1151-001/module-receipt.json"}, {"manifest": "tests/golden/WBV2-P1152-001/manifest.json", "report": "tests/golden/WBV2-P1152-001/module-receipt.json"}, ], "dependencyReuse": [ {"unitId": "modifier-source:MOD_grease_pencil_util", "representativeUnitId": "modifier-source:MOD_grease_pencil_array", "proofSource": "blender-5.2.0/source/blender/modifiers/intern/MOD_grease_pencil_array.cc", "marker": "MOD_grease_pencil_util.hh", "productionEvidence": "tests/golden/WBV2-P1151-001/modifier-reader-batch-production.json"}, {"unitId": "modifier-source:MOD_meshcache_mdd", "representativeUnitId": "modifier-source:MOD_meshcache", "proofSource": "blender-5.2.0/source/blender/modifiers/intern/MOD_meshcache.cc", "marker": "MOD_meshcache_read_mdd_times", "productionEvidence": "tests/golden/WBV2-P1150-001/modifier-reader-batch-production.json"}, {"unitId": "modifier-source:MOD_meshcache_pc2", "representativeUnitId": "modifier-source:MOD_meshcache", "proofSource": "blender-5.2.0/source/blender/modifiers/intern/MOD_meshcache.cc", "marker": "MOD_meshcache_read_pc2_times", "productionEvidence": "tests/golden/WBV2-P1150-001/modifier-reader-batch-production.json"}, {"unitId": "modifier-source:MOD_meshcache_util", "representativeUnitId": "modifier-source:MOD_meshcache", "proofSource": "blender-5.2.0/source/blender/modifiers/intern/MOD_meshcache.cc", "marker": "MOD_meshcache_util.hh", "productionEvidence": "tests/golden/WBV2-P1150-001/modifier-reader-batch-production.json"}, {"unitId": "modifier-source:MOD_weightvg_util", "representativeUnitId": "modifier-source:MOD_weightvgedit", "proofSource": "blender-5.2.0/source/blender/modifiers/intern/MOD_weightvgedit.cc", "marker": "MOD_weightvg_util.hh", "productionEvidence": "tests/golden/WBV2-P1152-001/modifier-reader-batch-production.json"}, ], }, { "moduleId": "MODIFIER_SOLIDIFY_HELPER_BATCH", "title": "verify solidify split helper sources", "owner": "MODIFIER", "identifiers": ["MOD_solidify_extrude", "MOD_solidify_nonmanifold"], "rna": [], "fixture": "tests/files/web/modifier_generate_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-multi-fixture-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": ["SOLIDIFY"], }, { "moduleId": "GREASE_PENCIL_MODIFIER_READER_BATCH", "title": "verify Grease Pencil modifier reader batch", "owner": "DRAWING", "identifiers": [ "MOD_grease_pencil_armature", "MOD_grease_pencil_array", "MOD_grease_pencil_build", "MOD_grease_pencil_color", "MOD_grease_pencil_dash", "MOD_grease_pencil_envelope", "MOD_grease_pencil_hook", "MOD_grease_pencil_lattice", "MOD_grease_pencil_length", "MOD_grease_pencil_mirror", "MOD_grease_pencil_multiply", "MOD_grease_pencil_noise", "MOD_grease_pencil_offset", "MOD_grease_pencil_opacity", "MOD_grease_pencil_outline", "MOD_grease_pencil_shrinkwrap", "MOD_grease_pencil_simplify", "MOD_grease_pencil_smooth", "MOD_grease_pencil_subdiv", "MOD_grease_pencil_texture", "MOD_grease_pencil_thickness", "MOD_grease_pencil_time", "MOD_grease_pencil_tint", "MOD_grease_pencil_weight_angle", "MOD_grease_pencil_weight_proximity", ], "rna": [ "GreasePencilArmatureModifier", "GreasePencilArrayModifier", "GreasePencilBuildModifier", "GreasePencilColorModifier", "GreasePencilEnvelopeModifier", "GreasePencilHookModifier", "GreasePencilLatticeModifier", "GreasePencilLengthModifier", "GreasePencilMirrorModifier", "GreasePencilMultiplyModifier", "GreasePencilNoiseModifier", "GreasePencilOffsetModifier", "GreasePencilOpacityModifier", "GreasePencilOutlineModifier", "GreasePencilShrinkwrapModifier", "GreasePencilSimplifyModifier", "GreasePencilSmoothModifier", "GreasePencilSubdivModifier", "GreasePencilTextureModifier", "GreasePencilTimeModifier", "GreasePencilTintModifier", "GreasePencilWeightAngleModifier", "GreasePencilWeightProximityModifier", ], "fixture": "tests/files/web/modifier_grease_pencil_scene.blend", "test": "web/tests/e2e/wbv2-modifier-reader-batch-two-production.spec.ts", "production": "tests/golden/{task}/modifier-reader-batch-production.json", "types": [ "GREASE_PENCIL_ARMATURE", "GREASE_PENCIL_ARRAY", "GREASE_PENCIL_BUILD", "GREASE_PENCIL_COLOR", "GREASE_PENCIL_DASH", "GREASE_PENCIL_ENVELOPE", "GREASE_PENCIL_HOOK", "GREASE_PENCIL_LATTICE", "GREASE_PENCIL_LENGTH", "GREASE_PENCIL_MIRROR", "GREASE_PENCIL_MULTIPLY", "GREASE_PENCIL_NOISE", "GREASE_PENCIL_OFFSET", "GREASE_PENCIL_OPACITY", "GREASE_PENCIL_OUTLINE", "GREASE_PENCIL_SHRINKWRAP", "GREASE_PENCIL_SIMPLIFY", "GREASE_PENCIL_SMOOTH", "GREASE_PENCIL_SUBDIV", "GREASE_PENCIL_TEXTURE", "GREASE_PENCIL_THICKNESS", "GREASE_PENCIL_TIME", "GREASE_PENCIL_TINT", "GREASE_PENCIL_VERTEX_WEIGHT_ANGLE", "GREASE_PENCIL_VERTEX_WEIGHT_PROXIMITY", ], }, ] # Source-backed operator recipes are the unattended fallback once the finite # preferred list is exhausted. Every recipe points at an existing native # command gate and fixture; no recipe is allowed to infer coverage from a # source filename alone. AUTO_OPERATOR_RECIPES = [ {"moduleId": "UV_UNWRAP_OPERATOR_BATCH", "title": "verify UV unwrap operator through native Main", "owner": "MESH", "identifier": "UV_OT_unwrap", "fixture": "tests/files/web/generated/WBV2-P3-001-MATERIAL.PBR_UV.blend", "test": "web/tests/e2e/wbv2-uv-unwrap-batch-production.spec.ts", "production": "tests/golden/{task}/uv-unwrap-production.json", "operation": "UV_UNWRAP"}, {"moduleId": "OBJECT_MATERIAL_SLOT_ASSIGN_OPERATOR_BATCH", "title": "verify material slot assign operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_material_slot_assign", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-material-slot-assign-batch-production.spec.ts", "production": "tests/golden/{task}/material-slot-assign-production.json", "operation": "MATERIAL_SLOT_ASSIGN"}, {"moduleId": "OBJECT_MATERIAL_SLOT_REMOVE_OPERATOR_BATCH", "title": "verify material slot remove operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_material_slot_remove", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-material-slot-production.spec.ts", "production": "tests/golden/{task}/material-slot-production.json", "operation": "MATERIAL_SLOT_REMOVE", "slotOperation": "REMOVE"}, {"moduleId": "MESH_DECIMATE_OPERATOR_BATCH", "title": "verify mesh decimate operator through native Main", "owner": "MESH", "identifier": "MESH_OT_decimate", "fixture": "tests/files/web/generated/WBV2-P4-001-MODIFIER.DECLARED_SLICES.blend", "test": "web/tests/e2e/wbv2-modifier-parameter-production.spec.ts", "production": "tests/golden/{task}/modifier-parameter-production.json", "operation": "DECIMATE"}, {"moduleId": "OBJECT_MATERIAL_SLOT_ADD_OPERATOR_BATCH", "title": "verify material slot add operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_material_slot_add", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-material-slot-production.spec.ts", "production": "tests/golden/{task}/material-slot-production.json", "operation": "MATERIAL_SLOT_ADD"}, {"moduleId": "OBJECT_JOIN_OPERATOR_BATCH", "title": "verify object join operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_join", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-mesh-object-operations-production.spec.ts", "production": "tests/golden/{task}/mesh-object-operations-production.json", "operation": "JOIN"}, {"moduleId": "OBJECT_LOCATION_CLEAR_OPERATOR_BATCH", "title": "verify object location clear operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_location_clear", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-transform-clear-batch-production.spec.ts", "production": "tests/golden/{task}/object-transform-clear-production.json", "operation": "LOCATION"}, {"moduleId": "OBJECT_ROTATION_CLEAR_OPERATOR_BATCH", "title": "verify object rotation clear operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_rotation_clear", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-transform-clear-batch-production.spec.ts", "production": "tests/golden/{task}/object-transform-clear-production.json", "operation": "ROTATION"}, {"moduleId": "OBJECT_SCALE_CLEAR_OPERATOR_BATCH", "title": "verify object scale clear operator through native Main", "owner": "OBJECT", "identifier": "OBJECT_OT_scale_clear", "fixture": "tests/files/web/basic_scene.blend", "test": "web/tests/e2e/wbv2-object-transform-clear-batch-production.spec.ts", "production": "tests/golden/{task}/object-transform-clear-production.json", "operation": "SCALE"}, {"moduleId": "MESH_EXTRUDE_OPERATOR_BATCH", "title": "verify mesh extrude operator through native Main", "owner": "MESH", "identifier": "MESH_OT_extrude_region", "fixture": "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend", "test": "web/tests/e2e/wbv2-mesh-topology-production.spec.ts", "production": "tests/golden/{task}/mesh-topology-production.json", "operation": "EXTRUDE"}, {"moduleId": "MESH_DISSOLVE_OPERATOR_BATCH", "title": "verify mesh dissolve operator through native Main", "owner": "MESH", "identifier": "MESH_OT_dissolve_edges", "fixture": "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend", "test": "web/tests/e2e/wbv2-mesh-topology-production.spec.ts", "production": "tests/golden/{task}/mesh-topology-production.json", "operation": "DISSOLVE"}, {"moduleId": "MESH_MERGE_OPERATOR_BATCH", "title": "verify mesh merge operator through native Main", "owner": "MESH", "identifier": "MESH_OT_merge", "fixture": "tests/files/web/generated/WBV2-P2-003-MESH.EDIT_CORE.blend", "test": "web/tests/e2e/wbv2-mesh-topology-production.spec.ts", "production": "tests/golden/{task}/mesh-topology-production.json", "operation": "MERGE"}, ] def sha(path: Path) -> str: return hashlib.sha256(path.read_bytes()).hexdigest() def write_json(path: Path, value: object) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") def write_text(path: Path, value: str) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text(value, encoding="utf-8") def write_planner_status(payload: dict) -> None: """Persist a deduplicated planner heartbeat for unattended observability.""" value = {"schemaVersion": 1, **payload} try: current = read_json(AUTO_PLANNER_STATUS) stable_current = {key: item for key, item in current.items() if key != "updatedAt"} stable_value = {key: item for key, item in value.items() if key != "updatedAt"} if stable_current == stable_value: return except (OSError, AttributeError): pass value["updatedAt"] = datetime.now(timezone.utc).isoformat() AUTO_PLANNER_STATUS.parent.mkdir(parents=True, exist_ok=True) temporary = AUTO_PLANNER_STATUS.with_suffix(AUTO_PLANNER_STATUS.suffix + f".tmp-{os.getpid()}") temporary.write_text(json.dumps(value, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") temporary.replace(AUTO_PLANNER_STATUS) def rel(path: Path) -> str: return path.relative_to(ROOT).as_posix() def batch_contract(task: str, batch: dict) -> dict: selected = [unit["id"] for unit in batch.get("units", [])] digest = hashlib.sha256(json.dumps(selected, ensure_ascii=False, separators=(",", ":")).encode()).hexdigest()[:16] return {"batchId": batch.get("batchId") or f"{task}:{batch['moduleId']}:{digest}", "selectedRecordIds": selected} def native_compositor_batch(inventory: dict) -> dict | None: """Select a bounded node-reader batch that can generate its own fixture.""" candidates = [ unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED" and unit.get("kind") == "node" and isinstance(unit.get("identifier"), str) and unit["identifier"].startswith("CompositorNode") ] if not candidates: return None # Native reader fixtures remain compact: some compositor node storage # layouts interact when more than five heterogeneous nodes share one # group. Keep the upper bound at the empirically stable shape; the # adaptive history may still reduce it after a focused gate failure. max_size = 5 module_id = "AUTO_COMPOSITOR_NATIVE_NODE_READER" history = [] for receipt_path in sorted(ROOT.glob("tests/golden/WBV2-P*-001/module-receipt.json"), key=lambda item: item.parent.name): receipt = read_json(receipt_path) if receipt.get("moduleId") != module_id: continue duration = receipt.get("efficiency", {}).get("totalDurationMs") if not isinstance(duration, (int, float)): duration = sum(item.get("durationMs", 0) for item in receipt.get("commands", []) if isinstance(item, dict)) history.append({ "durationMs": duration, "commands": len(receipt.get("commands", [])), "timeout": any("timeout" in str(item.get("output", "")).lower() for item in receipt.get("commands", []) if isinstance(item, dict)), "focusedGateFailure": receipt.get("status") != "PASS", }) recent = history[-3:] current_size = min(len(candidates), max_size) if recent and any(item["timeout"] or item["durationMs"] > 900_000 or item["commands"] > 48 for item in recent): next_size = max(1, current_size // 2) elif recent and all(item["durationMs"] < 300_000 and item["commands"] < 24 and not item["focusedGateFailure"] for item in recent): next_size = min(max_size, current_size + 4) else: next_size = current_size units = candidates[:next_size] return { "moduleId": module_id, "title": "verify auto-generated native compositor reader batch", "owner": "NODES", "identifiers": [unit["identifier"] for unit in units], "rna": [], "test": "web/tests/e2e/compositor-main-reader-production.spec.ts", "production": "tests/golden/{task}/compositor-main-reader-production.json", "types": [unit["identifier"] for unit in units], "units": units, "candidateUnits": candidates, "nativeCompositorFixture": True, "adaptiveKey": f"{module_id}:LOCAL_EXACT:NODES", "adaptiveHistory": recent, "adaptiveCurrentSize": current_size, "adaptiveNextSize": next_size, "adaptiveMinSize": 1, "adaptiveMaxSize": max_size, } def ensure_native_compositor_fixture(batch: dict, contract: dict) -> None: """Materialize a selected native node batch before its manifest references it.""" if not batch.get("nativeCompositorFixture"): return digest = contract["batchId"].rsplit(":", 1)[-1] fixture = Path(f"tests/files/web/generated/wbv2-compositor-{digest}.blend") batch["fixture"] = fixture.as_posix() if (ROOT / fixture).is_file(): return command = ["python3", "my-tools/generate_wbv2_compositor_fixture.py", "--output", fixture.as_posix()] for unit in batch["units"]: command.extend(["--node", unit["identifier"]]) result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False) if result.returncode: raise SystemExit(f"native fixture preparation failed for {batch['moduleId']}:\n{result.stdout}{result.stderr}") def ensure_native_geometry_fixture(batch: dict, contract: dict) -> None: if not batch.get("nativeGeometryFixture"): return digest = contract["batchId"].rsplit(":", 1)[-1] fixture = Path(f"tests/files/web/generated/wbv2-geometry-{digest}.blend") batch["fixture"] = fixture.as_posix() if fixture.is_file() or (ROOT / fixture).is_file(): return command = ["python3", "my-tools/generate_wbv2_geometry_fixture.py", "--output", fixture.as_posix()] for unit in batch["units"]: command.extend(["--node", unit["identifier"]]) result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False) if result.returncode: raise SystemExit(f"native geometry fixture preparation failed for {batch['moduleId']}:\n{result.stdout}{result.stderr}") def ensure_native_shader_fixture(batch: dict, contract: dict) -> None: if not batch.get("nativeShaderFixture"): return digest = contract["batchId"].rsplit(":", 1)[-1] fixture = Path(f"tests/files/web/generated/wbv2-shader-{digest}.blend") batch["fixture"] = fixture.as_posix() if (ROOT / fixture).is_file(): return command = ["python3", "my-tools/generate_wbv2_shader_fixture.py", "--output", fixture.as_posix()] for unit in batch["units"]: command.extend(["--node", unit["identifier"]]) result = subprocess.run(command, cwd=ROOT, text=True, capture_output=True, check=False) if result.returncode: raise SystemExit(f"native shader fixture preparation failed for {batch['moduleId']}:\n{result.stdout}{result.stderr}") def native_geometry_batch(inventory: dict) -> dict | None: candidates = [ unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED" and unit.get("kind") == "node" and isinstance(unit.get("identifier"), str) and (unit["identifier"].startswith("GeometryNode") or unit["identifier"].startswith("FunctionNode") or unit["identifier"].startswith("Node")) ] if not candidates: return None max_size = 5 units = candidates[:max_size] return { "moduleId": "AUTO_GEOMETRY_NATIVE_NODE_READER", "title": "verify auto-generated native geometry node reader batch", "owner": "NODES", "identifiers": [unit["identifier"] for unit in units], "rna": [], "types": [unit["identifier"] for unit in units], "units": units, "candidateUnits": candidates, "nativeGeometryFixture": True, "test": "web/tests/e2e/geometry-node-main-reader.spec.ts", "production": "tests/golden/{task}/geometry-node-reader-production.json", "adaptiveKey": "AUTO_GEOMETRY_NATIVE_NODE_READER:LOCAL_EXACT:NODES", "adaptiveHistory": [], "adaptiveCurrentSize": len(units), "adaptiveNextSize": len(units), "adaptiveMinSize": 1, "adaptiveMaxSize": max_size, } def native_shader_batch(inventory: dict) -> dict | None: candidates = [unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED" and unit.get("kind") == "node" and isinstance(unit.get("identifier"), str) and unit["identifier"].startswith("ShaderNode")] if not candidates: return None units = candidates[:5] return {"moduleId": "AUTO_SHADER_NATIVE_NODE_READER", "title": "verify auto-generated native shader node reader batch", "owner": "NODES", "identifiers": [u["identifier"] for u in units], "rna": [], "types": [u["identifier"] for u in units], "units": units, "candidateUnits": candidates, "nativeShaderFixture": True, "test": "web/tests/e2e/shader-main-reader-production.spec.ts", "production": "tests/golden/{task}/shader-main-reader-production.json", "adaptiveKey": "AUTO_SHADER_NATIVE_NODE_READER:LOCAL_EXACT:NODES", "adaptiveHistory": [], "adaptiveCurrentSize": len(units), "adaptiveNextSize": len(units), "adaptiveMinSize": 1, "adaptiveMaxSize": 5} def prepare_native_fixture(batch: dict, task: str, contract: dict) -> dict: """Generate a native fixture, shrinking only when Blender rejects a mix.""" if not (batch.get("nativeCompositorFixture") or batch.get("nativeGeometryFixture") or batch.get("nativeShaderFixture")): return contract while True: try: if batch.get("nativeCompositorFixture"): ensure_native_compositor_fixture(batch, contract) elif batch.get("nativeGeometryFixture"): ensure_native_geometry_fixture(batch, contract) else: ensure_native_shader_fixture(batch, contract) return contract except SystemExit: units = list(batch.get("units", [])) if len(units) <= 1: raise units = units[: max(1, len(units) // 2)] batch["units"] = units batch["identifiers"] = [unit["identifier"] for unit in units] batch["types"] = [unit["identifier"] for unit in units] batch.pop("fixture", None) contract = batch_contract(task, batch) def recover_blocked_native_batch(current: str, index: dict, plan: dict, inventory: dict) -> bool: """Shrink one failed auto-native batch to the last proven fixture shape. This only applies to the active task's recorded BLOCKED receipt. The failed receipt remains immutable evidence; the task contract is repaired in place so the scheduler can retry the same bounded increment. """ record = next((item for item in plan.get("tasks", []) if item.get("id") == current), None) module = record.get("module") if isinstance(record, dict) else None receipt_path = ROOT / "tests/golden" / current / "module-receipt.json" receipt = read_json(receipt_path) if receipt_path.is_file() else {} if not isinstance(module, dict) or not str(module.get("moduleId", "")).startswith(("AUTO_COMPOSITOR_NATIVE_NODE_READER", "AUTO_SHADER_NATIVE_NODE_READER")): if isinstance(module, dict) and str(module.get("moduleId", "")).startswith("MESH_") and receipt.get("status") == "BLOCKED" and module.get("commands"): operation = { "MESH_BEVEL_OPERATOR_BATCH": "BEVEL", "MESH_INSET_OPERATOR_BATCH": "INSET", "MESH_LOOPCUT_OPERATOR_BATCH": "LOOP_CUT", }.get(str(module.get("moduleId"))) command = module["commands"][-1] if operation and "WBV2_BATCH_OPERATION=" not in command: module["commands"][-1] = command.replace("WBV2_BATCH_TYPES=", f"WBV2_BATCH_OPERATION={operation} WBV2_BATCH_TYPES=", 1) write_json(ROOT / index["source"]["path"], plan) run_node("tools/web/generate-v2-task-index.mjs", "--plan-path", index["source"]["path"]) run_node("tools/web/check-task-context.mjs", "--task", current, "--write") print(f"wbv2-blocked-mesh-batch-recovered task={current} operation={operation}") return True return False if receipt.get("status") != "BLOCKED": return False by_id = {unit.get("id"): unit for unit in inventory.get("units", []) if isinstance(unit, dict)} selected_ids = list(receipt.get("selectedRecordIds", []))[:5] units = [by_id[unit_id] for unit_id in selected_ids if unit_id in by_id] if len(units) != len(selected_ids): return False is_shader = str(module["moduleId"]).startswith("AUTO_SHADER") if not is_shader and len(receipt.get("selectedRecordIds", [])) <= 5: return False batch = { "moduleId": str(module["moduleId"]), "title": "recover native compositor node reader batch", "owner": "NODES", "identifiers": [unit["identifier"] for unit in units], "rna": [], "types": [unit["identifier"] for unit in units], "units": units, "nativeCompositorFixture": True, "test": "web/tests/e2e/shader-main-reader-production.spec.ts" if is_shader else "web/tests/e2e/compositor-main-reader-production.spec.ts", "production": "tests/golden/{task}/shader-main-reader-production.json" if is_shader else "tests/golden/{task}/compositor-main-reader-production.json", "nativeShaderFixture": is_shader, "nativeCompositorFixture": not is_shader, } contract = batch_contract(current, batch) if is_shader: ensure_native_shader_fixture(batch, contract) else: ensure_native_compositor_fixture(batch, contract) fixture = batch["fixture"] source_paths = [unit["locations"][0]["path"] for unit in units] source_paths += [ "blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp", batch["test"], fixture, "web/app/src/vendor/blender/web_engine.wasm", ] types = json.dumps(batch["types"], separators=(",", ":")) ids = json.dumps(selected_ids, separators=(",", ":")) production = f"tests/golden/{current}/shader-main-reader-production.json" if is_shader else f"tests/golden/{current}/compositor-main-reader-production.json" command = ( f"WBV2_BATCH_TASK={current} WBV2_BATCH_PRODUCTION={production} " f"WBV2_BATCH_FIXTURE={fixture} WBV2_BATCH_TYPES='{types}' WBV2_BATCH_UNITS='{ids}' " f"WEB_TEST_PORT=5371 npm --prefix web run test:e2e -- --workers=1 {batch['test']}" ) module.update({ "batchId": contract["batchId"], "selectedRecordIds": selected_ids, "sourcePaths": source_paths, "positive": [f"{name} type is read from Blender Main" for name in batch["identifiers"]] + ["shared RNA metadata is preserved", "save/reopen preserves the batch metadata"], "commands": [ "source tools/web/emscripten-env.sh && cmake --build build_web_blender6 --target web_engine -- -j8 && cp build_web_blender6/bin/web_engine.wasm web/app/src/vendor/blender/web_engine.wasm && cp build_web_blender6/bin/web_engine.wasm web/app/public/vendor/blender/web_engine.wasm && node tools/web/split-wbv2-wasm.mjs --input web/app/public/vendor/blender/web_engine.wasm --output-dir web/app/public/vendor/blender/modules/single --split-funcs web_engine_get_live_handles && node tools/web/split-wbv2-wasm.mjs --input web/app/public/vendor/blender/pthread/web_engine.wasm --output-dir web/app/public/vendor/blender/modules/pthread --split-funcs web_engine_get_live_handles", "npm --prefix web run typecheck", "npm --prefix web run build", command, ], "coverageBindings": selected_ids, }) record["batchId"] = contract["batchId"] record["selectedRecordIds"] = selected_ids write_json(ROOT / index["source"]["path"], plan) run_node("tools/web/generate-v2-task-index.mjs", "--plan-path", index["source"]["path"]) card_path = ROOT / "docs/tasks" / f"{current}.md" write_text(card_path, make_card(current, record.get("dependsOn", [""])[-1], "in_progress", batch, f"node tools/web/run-wbv2-module-task.mjs --task {current} --write", contract)) run_node("tools/web/check-task-context.mjs", "--task", current, "--write") print(f"wbv2-blocked-batch-recovered task={current} batchId={contract['batchId']} selected={len(selected_ids)}") return True def discover_existing_evidence_batch(inventory: dict) -> dict | None: """Find exact Chromium receipts that list currently-unbound records. This is an evidence-only discovery path: it never infers coverage from a neighboring source file and never schedules a browser rerun. A new batch receives a copied, task-bound evidence record through the module runner. """ unresolved = {unit["id"]: unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED"} if not unresolved: return None operation_tests = ( ("FCURVE", "web/tests/e2e/wbv2-fcurve-modifier-production.spec.ts"), ("LINE_STYLE", "web/tests/e2e/wbv2-line-style-reader-production.spec.ts"), ("SHAPE_KEY", "web/tests/e2e/wbv2-shape-key-reader-production.spec.ts"), ("MODIFIER", "web/tests/e2e/wbv2-modifier-reader-batch-two-production.spec.ts"), ) for evidence_path in sorted(ROOT.glob("tests/golden/WBV2-P*-001/*.json"), key=lambda item: item.as_posix()): try: evidence = json.loads(evidence_path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): continue if evidence.get("browser") != "chromium" or evidence.get("queueMutation") is not False: continue source_units = evidence.get("sourceUnits") if not isinstance(source_units, list) or not source_units or not all(isinstance(unit, str) for unit in source_units): continue if not set(source_units).issubset(unresolved): continue operation = str(evidence.get("operation", "")) test = next((path for marker, path in operation_tests if marker in operation), None) fixture_paths = evidence.get("fixtures") if isinstance(evidence.get("fixtures"), list) else [evidence.get("fixture")] fixture_paths = [fixture for fixture in fixture_paths if isinstance(fixture, str) and (ROOT / fixture).is_file()] if not test or not (ROOT / test).is_file() or not fixture_paths: continue identifiers = [unit.split(":", 1)[1] for unit in source_units if unit.startswith("modifier-source:")] rna = [unit.split(":", 1)[1] for unit in source_units if unit.startswith("rna-struct:")] expected_types = evidence.get("expectedTypes") if isinstance(evidence.get("expectedTypes"), list) else [] return { "moduleId": f"AUTO_EVIDENCE_REUSE_{evidence_path.parent.name}", "title": f"reuse exact Chromium evidence from {evidence_path.parent.name}", "owner": str(next((unresolved[unit].get("family") for unit in source_units if unresolved[unit].get("family")), "PROGRAM")), "identifiers": identifiers, "rna": rna, "fixtures": fixture_paths, "test": test, "production": "tests/golden/{task}/auto-reused-production.json", "types": expected_types, "units": [unresolved[unit] for unit in source_units], "candidateUnits": [unresolved[unit] for unit in source_units], "reuseExistingEvidence": rel(evidence_path), "reuseEvidenceSource": rel(evidence_path), } def auto_operator_macro_batch(inventory: dict) -> dict | None: """Select a source-backed operator/macro batch from an existing native gate. The dynamic inventory contains many Blender UI macro identifiers whose executable behavior is represented by a smaller set of already-proven native commands. Keep this bridge explicit and bounded: it only maps identifiers to gates that already exercise the corresponding mutation, uses one unit per batch, and never changes coverage until Chromium emits an exact receipt for that selected source record. """ units = [ unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED" and unit.get("kind") in {"operator", "operator-macro"} and isinstance(unit.get("identifier"), str) ] if not units: return None # Names are exact on purpose. Shared words such as "delete" or # "duplicate" do not prove that a macro from another editor invokes the # same native command; substring matching would create false coverage. # No unresolved macro currently has an exact native gate. Keep the table # empty until a matching fixture and command are added; mapping a macro to # a merely similar operation would be false coverage. gates = () for exact_identifier, module_id, owner, fixture, test, production_name, operation in gates: if not (ROOT / fixture).is_file() or not (ROOT / test).is_file(): continue selected = next((unit for unit in units if unit["identifier"] == exact_identifier), None) if not selected: continue return { "moduleId": module_id, "title": f"verify {selected['identifier']} through existing native gate", "owner": owner, "identifiers": [selected["identifier"]], "rna": [], "fixture": fixture, "test": test, "production": f"tests/golden/{{task}}/{production_name}", "types": [], "batchOperation": operation, "units": [selected], "candidateUnits": [selected], "adaptiveKey": f"{module_id}:LOCAL_EXACT:{owner}", "adaptiveHistory": [], "adaptiveCurrentSize": 1, "adaptiveNextSize": 1, "adaptiveMinSize": 1, "adaptiveMaxSize": 1, } return None def pending_summary(inventory: dict) -> dict: pending = [unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED"] by_kind: dict[str, int] = {} by_family: dict[str, int] = {} for unit in pending: by_kind[unit.get("kind", "UNKNOWN")] = by_kind.get(unit.get("kind", "UNKNOWN"), 0) + 1 by_family[unit.get("family", "UNKNOWN")] = by_family.get(unit.get("family", "UNKNOWN"), 0) + 1 return {"count": len(pending), "byKind": by_kind, "byFamily": by_family, "sample": [unit.get("id") for unit in pending[:12]]} def write_source_gap_report(inventory: dict) -> None: """Persist the next bounded source gaps when no exact gate exists. This is a planner artifact, not coverage evidence. It gives the unattended loop a deterministic handoff for the next implementation pass without marking an unsupported unit as verified or repeatedly rescanning the tree. """ pending = [unit for unit in inventory.get("units", []) if isinstance(unit, dict) and unit.get("coverageStatus") != "VERIFIED"] report = { "schemaVersion": 1, "status": "awaiting_exact_native_gate", "pendingCount": len(pending), "next": [ { "unitId": unit.get("id"), "identifier": unit.get("identifier"), "kind": unit.get("kind"), "family": unit.get("family"), "source": (unit.get("locations") or [{}])[0].get("path"), "line": (unit.get("locations") or [{}])[0].get("line"), "required": "real Blender Main/WASM command adapter plus exact Chromium fixture", } for unit in pending[:64] ], } write_json(CONTROL_ROOT / "unresolved-source-batches.json", report) def run_node(*args: str) -> None: result = subprocess.run(["node", *args], cwd=ROOT, text=True, capture_output=True, check=False) output = result.stdout or result.stderr if output: print(output, end="" if output.endswith("\n") else "\n") if result.returncode: raise SystemExit(result.returncode) def current_task() -> str: match = re.search(r"\| 当前任务 \| `(WBV2-P(\d+)-(\d{3}))` \|", QUEUE.read_text(encoding="utf-8")) if not match: raise SystemExit("queue pointer is not a WBV2 task") return match.group(1) def queue_parent() -> str: match = re.search(r"\| parent manifest \| `([^`]+)` \|", QUEUE.read_text(encoding="utf-8"), re.IGNORECASE) if not match: raise SystemExit("queue parent manifest is missing") return match.group(1) def snapshot(text: str, task: str, parent: str, next_task: str) -> str: tick = "`" substitutions = [ (r"\| Current task when this file was written \| `[^`]+` \|", f"| Current task when this file was written | {tick}{task}{tick} |"), (r"\| Current task state \| `[^`]+` \|", f"| Current task state | {tick}in_progress{tick} |"), (r"\| Parent manifest \| `[^`]+` \|", f"| Parent manifest | {tick}tests/golden/{parent}/manifest.json{tick} |"), (r"\| Current task card \| `[^`]+` \|", f"| Current task card | {tick}docs/tasks/{task}.md{tick} |"), (r"\| Current task-context nextTask \| `[^`]+` \|", f"| Current task-context nextTask | {tick}{next_task}{tick} |"), (r"\| Focused command \| `[^`]+` \|", f"| Focused command | {tick}node tools/web/run-wbv2-module-task.mjs --task {task} --write{tick} |"), ] for pattern, replacement in substitutions: text = re.sub(pattern, replacement, text, count=1) return text def make_card(task: str, parent: str, status: str, batch: dict, entrypoint: str, contract: dict) -> str: fixture_paths = batch.get("fixtures") or ([batch["fixture"]] if batch.get("fixture") else []) fixture_text = ", ".join(f"`{fixture}`" for fixture in fixture_paths) if not fixture_text: fixture_text = "existing Chromium receipts" if task.endswith("-000"): return f"""# {task}: authorize module batch - task: {task} - parent: {parent} - status: {status} - gap: capability:{batch['moduleId']} - ownerFamily: {batch['owner']} - targetImplementationClass: QUEUE_ONLY - batchId: {contract['batchId']} - selectedRecordIds: {json.dumps(contract['selectedRecordIds'], ensure_ascii=False, separators=(',', ':'))} - commandBudget: max=48; read/exploration max=16 - Implementation entrypoint: `{entrypoint}` ## 目标 授权一个共享 Blender Main/WASM/Chromium 功能批次:{batch['moduleId']},共 {len(batch['identifiers']) + len(batch['rna'])} 个源码/RNA 单元。 ## 输入与范围 - Blender source: `blender-5.2.0/source/blender` - Fixtures/test: {fixture_text}, `{batch['test']}` ## 验收 - prior evidence and dynamic inventory remain hash-bound and immutable - one shared function chain validates the complete selected batch - missing source, unsupported data, hash drift or false completion remains blocked - `node tools/web/check-task-context.mjs --task {task} --write` ## 交付与回滚 - Manifest: `tests/golden/{task}/manifest.json` - Status: `docs/status/{task}.md` - Rollback: restore {parent} as queue parent. """ if len(batch["identifiers"]) + len(batch["rna"]) > 16: return f"""# {task}: {batch['title']} - task: {task} - parent: {parent} - status: {status} - gap: capability:{batch['moduleId']} - ownerFamily: {batch['owner']} - targetImplementationClass: LOCAL_EXACT - batchId: {contract['batchId']} - selectedRecordIds: {json.dumps(contract['selectedRecordIds'], ensure_ascii=False, separators=(',', ':'))} - commandBudget: max=48; read/exploration max=16 - Implementation entrypoint: `{entrypoint}` ## 目标 使用一个共享 Blender Main/WASM/Worker/Chromium 链验证 {len(batch['identifiers'])} 个 modifier source 与 {len(batch['rna'])} 个 RNA unit,形成一个聚合 receipt。 ## 输入与范围 - Blender source family: `blender-5.2.0/source/blender` - Fixtures: {fixture_text} - Chromium test: `{batch['test']}` - Unit selection: `{len(batch['identifiers']) + len(batch['rna'])} units`(完整列表只在 module plan/manifest) ## 验收 - one shared function chain covers every selected unit exactly once - malformed input, unsupported data and hash drift fail closed without Main mutation - save/reopen, deterministic second read and Chromium evidence are exact - `node tools/web/check-task-context.mjs --task {task} --write` ## 失败边界 - partial batch success never advances or claims omitted units - existing source/receipt hashes are reused; historical inventory remains read-only ## 交付与回滚 - Aggregate receipt: `tests/golden/{task}/module-receipt.json` - Production evidence: `tests/golden/{task}/modifier-reader-batch-production.json` - Manifest: `tests/golden/{task}/manifest.json` - Rollback: restore {parent} as queue parent. """ names = ", ".join(batch["identifiers"]) or "RNA-only reader units" units = ", ".join(f"`modifier-source:{name}`" for name in batch["identifiers"]) or "none" rna_units = ", ".join(f"`rna-struct:{name}`" for name in batch["rna"]) or "none (source-only dependency batch)" return f"""# {task}: {batch['title']} - task: {task} - parent: {parent} - status: {status} - gap: capability:{batch['moduleId']} - ownerFamily: {batch['owner']} - targetImplementationClass: LOCAL_EXACT - batchId: {contract['batchId']} - selectedRecordIds: {json.dumps(contract['selectedRecordIds'], ensure_ascii=False, separators=(',', ':'))} - commandBudget: max=48; read/exploration max=16 - Implementation entrypoint: `{entrypoint}` ## 目标 以一个共享 Blender Main reader、WASM build、Worker 路由和 Chromium receipt 批量验证 {names} 的真实 modifier/RNA 元数据。 ## 输入与范围 - Blender modifier sources: `blender-5.2.0/source/blender/modifiers/intern/` - RNA source: `blender-5.2.0/source/blender/makesrna/intern/rna_modifier.cc` - Native reader: `blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp` - Fixtures: {fixture_text} - Chromium test: `{batch['test']}` - Selected modifier units: {units} - Selected RNA units: {rna_units} ## 验收 - one shared Main/WASM/Worker/Chromium chain observes every selected type - malformed input is rejected without Main mutation - save/reopen and deterministic second read are exact - only Chromium is used; JS simulation and per-API duplicate workers are forbidden - each selected source unit receives an explicit hash-bound coverage binding - `node tools/web/check-task-context.mjs --task {task} --write` ## 失败边界 - any missing source, unsupported type, hash drift, malformed data or non-exact recovery keeps the batch `in_progress`/`blocked` - partial batch success never claims the omitted family; historical inventory remains read-only ## 交付与回滚 - Receipt: `tests/golden/{task}/module-receipt.json` - Aggregate production evidence: `tests/golden/{task}/modifier-reader-batch-production.json` - Manifest: `tests/golden/{task}/manifest.json` - Status: `docs/status/{task}.md` - Rollback: restore {parent} as queue parent without rewriting prior evidence. """ def select_batch(inventory: dict) -> dict: units = {unit.get("identifier"): unit for unit in inventory.get("units", []) if isinstance(unit, dict)} for candidate in PREFERRED_BATCHES: selected = [units.get(name) for name in candidate["identifiers"] + candidate["rna"]] if all(selected): unresolved = [unit for unit in selected if unit.get("coverageStatus") != "VERIFIED"] if unresolved: key = f"{candidate['moduleId']}:LOCAL_EXACT:{candidate['owner']}" history = [] for receipt_path in sorted(ROOT.glob("tests/golden/WBV2-P*-001/module-receipt.json"), key=lambda item: item.parent.name): receipt = read_json(receipt_path) if receipt.get("moduleId") != candidate["moduleId"]: continue duration = receipt.get("efficiency", {}).get("totalDurationMs") if not isinstance(duration, (int, float)): duration = sum(item.get("durationMs", 0) for item in receipt.get("commands", []) if isinstance(item, dict)) history.append({ "durationMs": duration, "commands": len(receipt.get("commands", [])), "timeout": any("timeout" in str(item.get("output", "")).lower() for item in receipt.get("commands", []) if isinstance(item, dict)), "focusedGateFailure": receipt.get("status") != "PASS", }) recent = history[-3:] min_size = 1 max_size = 8 if len(unresolved) > 16 or len(candidate.get("fixtures", [])) > 1 else 16 current_size = min(len(unresolved), max_size) if recent and any(item["timeout"] or item["durationMs"] > 900_000 or item["commands"] > 48 for item in recent): next_size = max(min_size, current_size // 2) elif recent and all(item["durationMs"] < 300_000 and item["commands"] < 24 and not item["focusedGateFailure"] for item in recent): next_size = min(max_size, current_size + 4) else: next_size = current_size chosen = unresolved[:max(min_size, min(max_size, len(unresolved), next_size))] return {**candidate, "units": chosen, "candidateUnits": selected, "adaptiveKey": key, "adaptiveHistory": recent, "adaptiveCurrentSize": current_size, "adaptiveNextSize": len(chosen), "adaptiveMinSize": min_size, "adaptiveMaxSize": max_size} discovered = discover_existing_evidence_batch(inventory) if discovered: return discovered native = native_compositor_batch(inventory) if native: return native geometry = native_geometry_batch(inventory) if geometry: return geometry shader = native_shader_batch(inventory) if shader: return shader for recipe in AUTO_OPERATOR_RECIPES: unit = units.get(recipe["identifier"]) if unit and unit.get("coverageStatus") != "VERIFIED" and (ROOT / recipe["fixture"]).is_file() and (ROOT / recipe["test"]).is_file(): return { "moduleId": recipe["moduleId"], "title": recipe["title"], "owner": recipe["owner"], "identifiers": [recipe["identifier"]], "rna": [], "fixture": recipe["fixture"], "test": recipe["test"], "production": recipe["production"], "types": [], "batchOperation": recipe["operation"], "clearMode": recipe.get("operation"), "units": [unit], "candidateUnits": [unit], "adaptiveKey": f"{recipe['moduleId']}:LOCAL_EXACT:{recipe['owner']}", "adaptiveHistory": [], "adaptiveCurrentSize": 1, "adaptiveNextSize": 1, "adaptiveMinSize": 1, "adaptiveMaxSize": 1, } macro = auto_operator_macro_batch(inventory) if macro: return macro summary = pending_summary(inventory) write_source_gap_report(inventory) write_planner_status({ "status": "waiting_for_exact_evidence", "reason": "no_configured_unresolved_source_batch", "pending": summary, "nextAction": "add a real fixture/adapter or exact Chromium receipt; do not fabricate coverage", }) raise SystemExit(f"no configured unresolved Blender source batch remains; pending={summary['count']} sample={summary['sample']}") def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--write", action="store_true") args = parser.parse_args() current = current_task() parent_path = queue_parent() current_manifest_path = ROOT / "tests/golden" / current / "manifest.json" current_manifest = read_json(current_manifest_path) index = read_json(INDEX) plan_path = ROOT / index["source"]["path"] plan = json.loads(plan_path.read_text(encoding="utf-8")) inventory = read_json(CONTROL_ROOT / "dynamic-inventory.json") if recover_blocked_native_batch(current, index, plan, inventory): return 0 terminal = current_manifest.get("status") == "done" and current_manifest.get("nextTask") is None and index.get("activeTask") is None if not terminal and not current_manifest.get("nextTask"): raise SystemExit(f"current task is not terminal: {current}") phase = max(int(item["id"].split("-")[1][1:]) for item in plan["tasks"]) + 1 root_task, module_task = f"WBV2-P{phase}-000", f"WBV2-P{phase}-001" batch = select_batch(inventory) contract = batch_contract(module_task, batch) batch["batchId"] = contract["batchId"] if batch.get("nativeCompositorFixture"): digest = contract["batchId"].rsplit(":", 1)[-1] batch["fixture"] = f"tests/files/web/generated/wbv2-compositor-{digest}.blend" if args.write: contract = prepare_native_fixture(batch, module_task, contract) if batch.get("nativeGeometryFixture"): digest = contract["batchId"].rsplit(":", 1)[-1] batch["fixture"] = f"tests/files/web/generated/wbv2-geometry-{digest}.blend" if args.write: contract = prepare_native_fixture(batch, module_task, contract) if batch.get("nativeShaderFixture"): digest = contract["batchId"].rsplit(":", 1)[-1] batch["fixture"] = f"tests/files/web/generated/wbv2-shader-{digest}.blend" if args.write: ensure_native_shader_fixture(batch, contract) batch["batchId"] = contract["batchId"] write_planner_status({ "status": "ready", "reason": "source_backed_batch_selected", "task": module_task, "moduleId": batch["moduleId"], "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "nextAction": "scheduler runs the focused module gate", }) if not args.write: print(json.dumps({"status": "ready", "rootTask": root_task, "nextTask": module_task, "moduleId": batch["moduleId"], "batchId": contract["batchId"], "batchSize": len(batch["units"]), "selectedRecordIds": contract["selectedRecordIds"], "sourceUnits": [unit["id"] for unit in batch["units"]], "adaptive": {"key": batch.get("adaptiveKey"), "history": batch.get("adaptiveHistory", []), "nextSize": batch.get("adaptiveNextSize", len(batch["units"]))}}, ensure_ascii=False)) return 0 current_status = ROOT / "docs/status" / f"{current}.md" if current_status.is_file(): status_text = re.sub(r"- nextTask: `[^`]+`", f"- nextTask: `{root_task}`", current_status.read_text(encoding="utf-8")) write_text(current_status, status_text) current_manifest["nextTask"] = root_task if isinstance(current_manifest.get("artifacts"), dict) and isinstance(current_manifest["artifacts"].get("status"), dict): current_manifest["artifacts"]["status"]["sha256"] = sha(current_status) write_json(current_manifest_path, current_manifest) source_paths = [unit["locations"][0]["path"] for unit in batch["units"]] fixture_paths = batch.get("fixtures") or ([batch["fixture"]] if batch.get("fixture") else []) if batch.get("dependencyReuse"): source_paths += [dependency["proofSource"] for dependency in batch["dependencyReuse"]] source_paths += [dependency["productionEvidence"] for dependency in batch["dependencyReuse"]] source_paths += [batch["test"]] else: source_paths += [ "blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp", batch["test"], *fixture_paths, "web/app/src/vendor/blender/web_engine.wasm", "web/app/public/vendor/blender/web_engine.wasm", "web/app/public/wasm-module-manifest.json", "web/app/public/vendor/blender/modules/single/web_engine.secondary.wasm", "web/app/public/vendor/blender/modules/pthread/web_engine.secondary.wasm", ] batch_types = json.dumps(batch["types"], separators=(',', ':')) batch_units = json.dumps([unit["id"] for unit in batch["units"]], separators=(',', ':')) if batch.get("dependencyReuse"): fixture_env = "" elif batch.get("fixtures"): fixture_env = f"WBV2_BATCH_FIXTURES='{json.dumps(fixture_paths, separators=(',', ':'))}'" else: fixture_env = f"WBV2_BATCH_FIXTURE={batch['fixture']}" production_env = f"WBV2_BATCH_PRODUCTION={batch['production'].format(task=module_task)}" operation_env = f"WBV2_BATCH_OPERATION={batch['batchOperation']}" if batch.get("batchOperation") else "" clear_mode_env = f"WBV2_BATCH_CLEAR_MODE={batch['clearMode']}" if batch.get("clearMode") else "" slot_operation_env = f"WBV2_BATCH_SLOT_OPERATION={batch['slotOperation']}" if batch.get("slotOperation") else "" if batch.get("dependencyReuse"): commands = [f"node tools/web/reuse-wbv2-source-dependencies.mjs --task {module_task} --write"] else: commands = [ "source tools/web/emscripten-env.sh && cmake --build build_web_blender6 --target web_engine -- -j8 && cp build_web_blender6/bin/web_engine.wasm web/app/src/vendor/blender/web_engine.wasm && cp build_web_blender6/bin/web_engine.wasm web/app/public/vendor/blender/web_engine.wasm && node tools/web/split-wbv2-wasm.mjs --input web/app/public/vendor/blender/web_engine.wasm --output-dir web/app/public/vendor/blender/modules/single --split-funcs web_engine_get_live_handles && node tools/web/split-wbv2-wasm.mjs --input web/app/public/vendor/blender/pthread/web_engine.wasm --output-dir web/app/public/vendor/blender/modules/pthread --split-funcs web_engine_get_live_handles", "npm --prefix web run typecheck", "npm --prefix web run build", f"WBV2_BATCH_TASK={module_task} {production_env} {fixture_env} {operation_env} {clear_mode_env} {slot_operation_env} WBV2_BATCH_TYPES='{batch_types}' WBV2_BATCH_UNITS='{batch_units}' WEB_TEST_PORT=5371 npm --prefix web run test:e2e -- --workers=1 tests/e2e/{Path(batch['test']).name}", ] root_record = {"id": root_task, "phase": str(phase), "title": "authorize_modifier_reader_batch", "kind": "enabling", "owner": "PROGRAM", "dependsOn": [current], "parityId": None, "implementationClass": "QUEUE_ONLY", "parallelGroup": f"module-root-{phase}", "gate": "MODULE", "card": f"docs/tasks/{root_task}.md", "behavior": "Authorize one shared source-backed modifier reader batch.", "outputs": ["successor authorization", "module plan", "manifest", "status"], "acceptance": ["prior evidence remains hash-bound", "batch source mapping is explicit", "inventory remains source-derived"], "commands": ["node tools/web/check-execution-control-plane.mjs"], "status": "done", "ready": False, "queueMutationAllowed": True} module_record = {"id": module_task, "phase": str(phase), "title": batch["title"], "kind": "module", "owner": batch["owner"], "dependsOn": [root_task], "parityId": batch["moduleId"], "implementationClass": "LOCAL_EXACT", "parallelGroup": f"module-{batch['moduleId'].lower()}", "gate": "MODULE", "card": f"docs/tasks/{module_task}.md", "behavior": f"Verify the {batch['moduleId']} batch through Blender Main, WASM and Chromium recovery.", "outputs": ["aggregate module receipt", "coverage bindings", "manifest", "status"], "acceptance": ["source and protocol hashes are bound", "every selected Main reader type is proven", "negative no-mutation and recovery are proven", "Chromium and save/reopen evidence are exact"], "status": "in_progress", "ready": True, "queueMutationAllowed": True, "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "module": {"moduleId": batch["moduleId"], "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "sourceBacked": True, "capabilities": [f"CAP-{batch['moduleId']}-READER", f"CAP-{batch['moduleId']}-RECOVERY", f"CAP-{batch['moduleId']}-FAIL-CLOSED"], "sourcePaths": source_paths, "reuseReceipts": batch.get("reuseReceipts", [{"manifest": "tests/golden/WBV2-P1107-000/manifest.json", "report": "tests/golden/WBV2-P1107-000/module-receipt.json"}]), "dependencyReuse": batch.get("dependencyReuse", []), "positive": [f"{name} type is read from Blender Main" for name in batch["identifiers"]] + ["shared RNA metadata is preserved", "save/reopen preserves the batch metadata"], "negative": ["malformed blend is rejected without Main mutation", "unsupported batch data fails closed", "non-finite metadata is not synthesized"], "recovery": ["save/reopen", "worker termination", "deterministic second read"], "commands": commands, "fullFamilyParity": False, "evidencePaths": [batch["production"].format(task=module_task)], "coverageBindings": contract["selectedRecordIds"]}} module_record["module"]["commandBudget"] = {"maxCommands": 48, "maxReadCommands": 16} if batch.get("reuseExistingEvidence"): module_record["module"]["reuseExistingEvidence"] = batch["reuseExistingEvidence"] root_out = CONTROL_ROOT.parent / root_task root_plan_path = root_out / "module-execution-plan.json" root_plan = {**plan, "planId": f"BLENDER_WEB_MODULE_EXECUTION_P{phase}", "status": "ACTIVE_MODULE_QUEUE", "tasks": [*plan["tasks"], root_record, module_record], "modules": [*plan.get("modules", []), {"id": batch["moduleId"], "task": module_task, "owner": batch["owner"], "sourceBacked": True, "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "capabilities": module_record["module"]["capabilities"], "sourcePaths": source_paths, "reuseReceipts": module_record["module"]["reuseReceipts"], "fullFamilyParity": False}]} write_json(root_plan_path, root_plan) root_card_path, module_card_path = ROOT / "docs/tasks" / f"{root_task}.md", ROOT / "docs/tasks" / f"{module_task}.md" write_text(root_card_path, make_card(root_task, current, "done", batch, f"python3 my-tools/authorize_next_wbv2_task.py --write", contract)) write_text(module_card_path, make_card(module_task, root_task, "in_progress", batch, f"node tools/web/run-wbv2-module-task.mjs --task {module_task} --write", contract)) root_report_path, root_status_path, root_manifest_path = root_out / "successor-authorization-report.json", ROOT / "docs/status" / f"{root_task}.md", root_out / "manifest.json" write_json(root_report_path, {"schemaVersion": 1, "task": root_task, "operation": "AUTHORIZE_SOURCE_BACKED_MODULE_BATCH", "taskClass": "QUEUE_ENABLEMENT", "status": "PASS", "parent": {"task": current, "manifest": rel(current_manifest_path), "manifestSha256": sha(current_manifest_path)}, "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "module": {"moduleId": batch["moduleId"], "task": module_task, "batchSize": len(batch["units"]), "candidateBatchSize": len(batch.get("candidateUnits", batch["units"])), "sourceUnits": contract["selectedRecordIds"], "sourceBacked": True}, "inventory": {"unitCount": inventory["summary"]["unitCount"], "verifiedUnits": inventory["coverage"]["verifiedUnits"], "unverifiedUnits": inventory["coverage"]["unverifiedUnits"], "sourceTreeSha256": inventory["authority"]["sourceTreeSha256"], "boundUnitDelta": 0}, "efficiency": {"existingBindingsReused": len(batch.get("candidateUnits", batch["units"])) - len(batch["units"]), "selectionPolicy": "all-candidate-units-then-unresolved-only", "adaptiveKey": batch.get("adaptiveKey"), "adaptiveHistory": batch.get("adaptiveHistory", []), "adaptiveNextSize": batch.get("adaptiveNextSize", len(batch["units"]))}, "nextTask": module_task}) write_text(root_status_path, f"# {root_task} status\n\n- task: `{root_task}`; parent: `{current}`; status: `done`\n- taskClass: `QUEUE_ENABLEMENT`; no modifier-family parity completion claimed\n- source-backed batch authorized: `{batch['moduleId']}` ({len(batch['units'])} units)\n- dynamic inventory: `{inventory['summary']['unitCount']}` units; verified `{inventory['coverage']['verifiedUnits']}`; unverified `{inventory['coverage']['unverifiedUnits']}`; bound delta `0`\n- nextTask: `{module_task}`\n") write_json(root_manifest_path, {"schemaVersion": 1, "task": root_task, "parentTask": current, "operation": "AUTHORIZE_SOURCE_BACKED_MODULE_BATCH", "status": "done", "enablingTask": True, "parityStateChange": False, "runtime": "MODULE_EXECUTION_QUEUE_BRIDGE", "batchId": contract["batchId"], "selectedRecordIds": contract["selectedRecordIds"], "artifacts": {"parentManifest": {"path": rel(current_manifest_path), "sha256": sha(current_manifest_path)}, "plan": {"path": rel(root_plan_path), "sha256": sha(root_plan_path)}, "report": {"path": rel(root_report_path), "sha256": sha(root_report_path)}, "status": {"path": rel(root_status_path), "sha256": sha(root_status_path)}}, "assertions": {"sourceBackedSuccessor": True, "moduleId": batch["moduleId"], "batchSize": len(batch["units"]), "inventoryTotal": 6900, "effectiveCompleted": 290, "effectivePending": 6609, "dynamicUnitCount": inventory["summary"]["unitCount"], "dynamicVerifiedUnits": inventory["coverage"]["verifiedUnits"], "dynamicUnverifiedUnits": inventory["coverage"]["unverifiedUnits"], "boundUnitDelta": 0, "fullHistoricalReproduction": False}, "nextTask": module_task}) run_node("tools/web/generate-v2-task-index.mjs", "--plan-path", rel(root_plan_path)) queue_text = QUEUE.read_text(encoding="utf-8") queue_text = re.sub(r"\| 当前任务 \| `[^`]+` \|", f"| 当前任务 | `{module_task}` |", queue_text, count=1) queue_text = re.sub(r"\| parent manifest \| `[^`]+` \|", f"| parent manifest | `tests/golden/{root_task}/manifest.json` |", queue_text, count=1, flags=re.IGNORECASE) queue_text = re.sub(r"\| 任务卡 \| \[[^\]]+\]\([^\)]+\) \|", f"| 任务卡 | [`tasks/{module_task}.md`](tasks/{module_task}.md) |", queue_text, count=1) write_text(QUEUE, queue_text) next_path = ROOT / "nextTask.md" if next_path.is_file(): write_text(next_path, snapshot(next_path.read_text(encoding="utf-8"), module_task, root_task, "NONE")) # Do not rerun the legacy hard-coded reconciliation here: it would discard # newer explicit bindings produced by completed module batches. The active # module's binder owns the incremental inventory update. # The root is a completed queue-enable record. Preparing its context again # wastes budget and can fail solely because the parent manifest grew; only # the active implementation batch needs a packet. print(f"wbv2-successor-authorized root={root_task} next={module_task} module={batch['moduleId']} batchSize={len(batch['units'])} reused={len(batch.get('candidateUnits', batch['units'])) - len(batch['units'])} inventoryDelta=0") return 0 if __name__ == "__main__": raise SystemExit(main())