@alpsckr/unitycli 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +5 -5
  2. package/contracts/command-contract.json +109 -217
  3. package/contracts/package-files.json +374 -294
  4. package/contracts/stable-session/direct-call-semantics.json +8 -2
  5. package/contracts/tool-completion-contract.json +148 -672
  6. package/dist/bridge/admission.d.ts +17 -1
  7. package/dist/bridge/admission.js +2 -0
  8. package/dist/bridge/admission.js.map +1 -1
  9. package/dist/bridge/domains/asset.js +3 -1
  10. package/dist/bridge/domains/asset.js.map +1 -1
  11. package/dist/bridge/domains/camera.js +39 -7
  12. package/dist/bridge/domains/camera.js.map +1 -1
  13. package/dist/bridge/domains/console.js +2 -1
  14. package/dist/bridge/domains/console.js.map +1 -1
  15. package/dist/bridge/domains/doctor.d.ts +3 -0
  16. package/dist/bridge/domains/doctor.js +8 -0
  17. package/dist/bridge/domains/doctor.js.map +1 -0
  18. package/dist/bridge/domains/editor.js +0 -4
  19. package/dist/bridge/domains/editor.js.map +1 -1
  20. package/dist/bridge/domains/game-object.js +4 -12
  21. package/dist/bridge/domains/game-object.js.map +1 -1
  22. package/dist/bridge/domains/graphics.js +1 -16
  23. package/dist/bridge/domains/graphics.js.map +1 -1
  24. package/dist/bridge/domains/object.js +3 -0
  25. package/dist/bridge/domains/object.js.map +1 -1
  26. package/dist/bridge/domains/physics.js +1 -2
  27. package/dist/bridge/domains/physics.js.map +1 -1
  28. package/dist/bridge/domains/profiler.js +18 -7
  29. package/dist/bridge/domains/profiler.js.map +1 -1
  30. package/dist/bridge/domains/project-tool.js +4 -0
  31. package/dist/bridge/domains/project-tool.js.map +1 -1
  32. package/dist/bridge/domains/reflection.js +10 -2
  33. package/dist/bridge/domains/reflection.js.map +1 -1
  34. package/dist/bridge/domains/runtime-tool.js +3 -0
  35. package/dist/bridge/domains/runtime-tool.js.map +1 -1
  36. package/dist/bridge/domains/scene.js +3 -8
  37. package/dist/bridge/domains/scene.js.map +1 -1
  38. package/dist/bridge/domains/screenshot.js +30 -6
  39. package/dist/bridge/domains/screenshot.js.map +1 -1
  40. package/dist/bridge/domains/ui.js +3 -1
  41. package/dist/bridge/domains/ui.js.map +1 -1
  42. package/dist/bridge/index.d.ts +5 -5
  43. package/dist/bridge/index.js +6 -9
  44. package/dist/bridge/index.js.map +1 -1
  45. package/dist/bridge/registry.js +3 -5
  46. package/dist/bridge/registry.js.map +1 -1
  47. package/dist/bridge/session-supervisor.d.ts +6 -3
  48. package/dist/bridge/session-supervisor.js +145 -39
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/start-diagnostics.d.ts +19 -0
  51. package/dist/bridge/start-diagnostics.js +96 -0
  52. package/dist/bridge/start-diagnostics.js.map +1 -0
  53. package/dist/bridge/types.d.ts +6 -5
  54. package/dist/bridge/types.js +16 -2
  55. package/dist/bridge/types.js.map +1 -1
  56. package/dist/catalog-protocol.d.ts +64 -0
  57. package/dist/catalog-protocol.js +460 -0
  58. package/dist/catalog-protocol.js.map +1 -0
  59. package/dist/cli-commands/command.js +84 -61
  60. package/dist/cli-commands/command.js.map +1 -1
  61. package/dist/cli-commands/extensions.js +70 -50
  62. package/dist/cli-commands/extensions.js.map +1 -1
  63. package/dist/cli-commands/instances.js +1 -1
  64. package/dist/cli-commands/instances.js.map +1 -1
  65. package/dist/cli-commands/package.js +10 -2
  66. package/dist/cli-commands/package.js.map +1 -1
  67. package/dist/cli-process-owners.d.ts +6 -0
  68. package/dist/cli-process-owners.js +27 -0
  69. package/dist/cli-process-owners.js.map +1 -0
  70. package/dist/cli.d.ts +1 -1
  71. package/dist/cli.js +20 -5
  72. package/dist/cli.js.map +1 -1
  73. package/dist/contracts/command-contract.js +14 -4
  74. package/dist/contracts/command-contract.js.map +1 -1
  75. package/dist/doctor.d.ts +4 -6
  76. package/dist/doctor.js +451 -398
  77. package/dist/doctor.js.map +1 -1
  78. package/dist/editor-blocker-observer.d.ts +2 -1
  79. package/dist/editor-blocker-observer.js +40 -13
  80. package/dist/editor-blocker-observer.js.map +1 -1
  81. package/dist/extension-availability.d.ts +27 -9
  82. package/dist/extension-availability.js +96 -56
  83. package/dist/extension-availability.js.map +1 -1
  84. package/dist/extensions/descriptor-loader.d.ts +2 -2
  85. package/dist/extensions/descriptor-loader.js +3 -4
  86. package/dist/extensions/descriptor-loader.js.map +1 -1
  87. package/dist/extensions/index.d.ts +1 -1
  88. package/dist/extensions/index.js +1 -1
  89. package/dist/extensions/index.js.map +1 -1
  90. package/dist/extensions/installer.d.ts +57 -5
  91. package/dist/extensions/installer.js +263 -64
  92. package/dist/extensions/installer.js.map +1 -1
  93. package/dist/extensions/registry.js +3 -5
  94. package/dist/extensions/registry.js.map +1 -1
  95. package/dist/extensions/state-contract.d.ts +11 -0
  96. package/dist/extensions/state-contract.js +50 -0
  97. package/dist/extensions/state-contract.js.map +1 -0
  98. package/dist/instances.d.ts +19 -1
  99. package/dist/instances.js +39 -0
  100. package/dist/instances.js.map +1 -1
  101. package/dist/package-manager.d.ts +0 -24
  102. package/dist/package-manager.js +5 -58
  103. package/dist/package-manager.js.map +1 -1
  104. package/dist/project-tool-discovery.d.ts +5 -2
  105. package/dist/project-tool-discovery.js +22 -18
  106. package/dist/project-tool-discovery.js.map +1 -1
  107. package/dist/runtime-code.js +1 -1
  108. package/dist/runtime-code.js.map +1 -1
  109. package/dist/tool-name.d.ts +4 -0
  110. package/dist/tool-name.js +15 -0
  111. package/dist/tool-name.js.map +1 -0
  112. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  113. package/dist/tools/.generated-completion-registry.js +763 -325
  114. package/dist/tools/.generated-completion-registry.js.map +1 -1
  115. package/dist/tools/.generated-registry.d.ts +6 -11
  116. package/dist/tools/.generated-registry.js +6 -11
  117. package/dist/tools/.generated-registry.js.map +1 -1
  118. package/dist/tools/.generated-schemas.js +11 -16
  119. package/dist/tools/.generated-schemas.js.map +1 -1
  120. package/dist/tools/asset/refresh-status.d.ts +3 -0
  121. package/dist/tools/asset/refresh-status.js +12 -0
  122. package/dist/tools/asset/refresh-status.js.map +1 -0
  123. package/dist/tools/asset/refresh.js +7 -3
  124. package/dist/tools/asset/refresh.js.map +1 -1
  125. package/dist/tools/build/player-status.d.ts +3 -0
  126. package/dist/tools/build/player-status.js +12 -0
  127. package/dist/tools/build/player-status.js.map +1 -0
  128. package/dist/tools/build/run.js +1 -1
  129. package/dist/tools/build/run.js.map +1 -1
  130. package/dist/tools/catalog-control.d.ts +22 -0
  131. package/dist/tools/catalog-control.js +335 -0
  132. package/dist/tools/catalog-control.js.map +1 -0
  133. package/dist/tools/catalog.d.ts +7 -9
  134. package/dist/tools/catalog.js +66 -45
  135. package/dist/tools/catalog.js.map +1 -1
  136. package/dist/tools/commands.d.ts +1 -1
  137. package/dist/tools/commands.js +44 -11
  138. package/dist/tools/commands.js.map +1 -1
  139. package/dist/tools/define.js +3 -2
  140. package/dist/tools/define.js.map +1 -1
  141. package/dist/tools/dispatcher.d.ts +53 -6
  142. package/dist/tools/dispatcher.js +277 -108
  143. package/dist/tools/dispatcher.js.map +1 -1
  144. package/dist/tools/editor/compile.js +1 -1
  145. package/dist/tools/editor/compile.js.map +1 -1
  146. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  147. package/dist/tools/editor/play-mode-status.js +12 -0
  148. package/dist/tools/editor/play-mode-status.js.map +1 -0
  149. package/dist/tools/editor/readiness.d.ts +0 -1
  150. package/dist/tools/editor/readiness.js +0 -1
  151. package/dist/tools/editor/readiness.js.map +1 -1
  152. package/dist/tools/editor/refresh.d.ts +7 -3
  153. package/dist/tools/editor/refresh.js +20 -15
  154. package/dist/tools/editor/refresh.js.map +1 -1
  155. package/dist/tools/editor/state.d.ts +2 -2
  156. package/dist/tools/editor/state.js +13 -12
  157. package/dist/tools/editor/state.js.map +1 -1
  158. package/dist/tools/metadata/derived.js +1 -7
  159. package/dist/tools/metadata/derived.js.map +1 -1
  160. package/dist/tools/metadata/status.js +1 -12
  161. package/dist/tools/metadata/status.js.map +1 -1
  162. package/dist/tools/metadata/types.d.ts +0 -2
  163. package/dist/tools/navmesh/live.js +1 -1
  164. package/dist/tools/navmesh/live.js.map +1 -1
  165. package/dist/tools/occlusion/live.js +1 -1
  166. package/dist/tools/occlusion/live.js.map +1 -1
  167. package/dist/tools/package/deploy.js +2 -2
  168. package/dist/tools/package/deploy.js.map +1 -1
  169. package/dist/tools/package/embed.js +1 -1
  170. package/dist/tools/package/embed.js.map +1 -1
  171. package/dist/tools/package/install-status.d.ts +3 -0
  172. package/dist/tools/package/install-status.js +12 -0
  173. package/dist/tools/package/install-status.js.map +1 -0
  174. package/dist/tools/package/install.js +3 -3
  175. package/dist/tools/package/install.js.map +1 -1
  176. package/dist/tools/package/live-resolve.js +45 -12
  177. package/dist/tools/package/live-resolve.js.map +1 -1
  178. package/dist/tools/package/registry-clear.js.map +1 -1
  179. package/dist/tools/package/registry-set.js.map +1 -1
  180. package/dist/tools/package/remove-status.d.ts +3 -0
  181. package/dist/tools/package/remove-status.js +12 -0
  182. package/dist/tools/package/remove-status.js.map +1 -0
  183. package/dist/tools/package/remove.js +3 -3
  184. package/dist/tools/package/remove.js.map +1 -1
  185. package/dist/tools/package/resolve.js +3 -3
  186. package/dist/tools/package/resolve.js.map +1 -1
  187. package/dist/tools/package/restore.js +2 -2
  188. package/dist/tools/package/restore.js.map +1 -1
  189. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  190. package/dist/tools/status/support.d.ts +13 -0
  191. package/dist/tools/status/support.js +28 -0
  192. package/dist/tools/status/support.js.map +1 -0
  193. package/dist/tools/test/cancel.js +2 -2
  194. package/dist/tools/test/cancel.js.map +1 -1
  195. package/dist/tools/test/run-status.d.ts +3 -0
  196. package/dist/tools/test/run-status.js +12 -0
  197. package/dist/tools/test/run-status.js.map +1 -0
  198. package/dist/tools/test/run.js +7 -4
  199. package/dist/tools/test/run.js.map +1 -1
  200. package/dist/tools/tools/group-summaries.js +10 -14
  201. package/dist/tools/tools/group-summaries.js.map +1 -1
  202. package/docs/architecture.md +4 -4
  203. package/docs/availability-differences.md +4 -4
  204. package/docs/command-execution.md +4 -3
  205. package/docs/error-codes.md +13 -5
  206. package/docs/extensions.md +1 -7
  207. package/docs/risk-and-side-effects.md +4 -3
  208. package/docs/safety-boundaries.md +3 -1
  209. package/docs/target-and-instance.md +1 -1
  210. package/docs/tool-discovery.md +17 -10
  211. package/docs/tool-execution.md +18 -7
  212. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  213. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  214. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  215. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  216. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  217. package/package.json +2 -4
  218. package/skills/unitycli/SKILL.md +11 -2
  219. package/skills/unitycli/manifest.json +1 -1
  220. package/skills/unitycli/references/operation-protocol.md +3 -3
  221. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  222. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  223. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  224. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  225. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  226. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  227. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  228. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  229. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  230. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  231. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  232. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  233. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  234. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  235. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  236. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  237. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  238. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  239. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  240. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  241. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  242. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  244. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  245. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  246. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  247. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  248. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  249. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  250. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  251. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  252. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  253. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  254. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  255. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  256. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  257. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  258. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  259. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  260. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  261. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  262. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  263. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  264. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  265. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  266. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  267. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  268. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  269. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  270. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  271. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  272. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  273. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  274. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  275. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  276. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  277. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  278. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  279. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  280. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  281. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  282. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  283. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  284. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  285. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  286. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  287. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  288. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  289. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  290. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  291. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  292. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  293. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  294. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  295. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  296. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  297. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  298. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  299. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  300. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  301. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  302. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  303. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  304. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  305. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  306. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  307. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  308. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  309. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  310. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  311. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  312. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  313. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  314. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  315. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  316. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  317. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  318. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  319. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  320. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  321. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  322. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  323. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  324. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  325. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  326. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  327. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  328. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  329. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  330. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  331. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  332. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  333. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  334. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  335. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  336. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  337. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  338. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  339. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  340. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  341. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  342. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  343. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  344. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  345. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  346. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  347. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  348. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  349. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  350. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  351. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  352. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  353. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  354. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  355. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  356. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  357. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  358. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  359. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  360. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  361. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  362. package/unitypkg/package.json +1 -1
  363. package/contracts/static-capability-catalog.json +0 -48226
  364. package/dist/bridge/domains/diagnose.d.ts +0 -2
  365. package/dist/bridge/domains/diagnose.js +0 -11
  366. package/dist/bridge/domains/diagnose.js.map +0 -1
  367. package/dist/bridge/domains/extension.d.ts +0 -8
  368. package/dist/bridge/domains/extension.js +0 -20
  369. package/dist/bridge/domains/extension.js.map +0 -1
  370. package/dist/capabilities.d.ts +0 -5
  371. package/dist/capabilities.js +0 -84
  372. package/dist/capabilities.js.map +0 -1
  373. package/dist/static-capability-catalog.d.ts +0 -13
  374. package/dist/static-capability-catalog.js +0 -41
  375. package/dist/static-capability-catalog.js.map +0 -1
  376. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  377. package/dist/tools/diagnose/capabilities.js +0 -34
  378. package/dist/tools/diagnose/capabilities.js.map +0 -1
  379. package/dist/tools/package/info.d.ts +0 -19
  380. package/dist/tools/package/info.js +0 -27
  381. package/dist/tools/package/info.js.map +0 -1
  382. package/dist/tools/package/list.d.ts +0 -27
  383. package/dist/tools/package/list.js +0 -21
  384. package/dist/tools/package/list.js.map +0 -1
  385. package/dist/tools/package/registry-get.d.ts +0 -10
  386. package/dist/tools/package/registry-get.js +0 -22
  387. package/dist/tools/package/registry-get.js.map +0 -1
  388. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  389. package/dist/tools/package/scoped-registry-list.js +0 -22
  390. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  391. package/dist/tools/package/search.d.ts +0 -17
  392. package/dist/tools/package/search.js +0 -39
  393. package/dist/tools/package/search.js.map +0 -1
  394. package/dist/tools/package/status.d.ts +0 -21
  395. package/dist/tools/package/status.js +0 -37
  396. package/dist/tools/package/status.js.map +0 -1
  397. package/dist/tools/tools/describe.d.ts +0 -27
  398. package/dist/tools/tools/describe.js +0 -35
  399. package/dist/tools/tools/describe.js.map +0 -1
  400. package/dist/tools/tools/group-list.d.ts +0 -16
  401. package/dist/tools/tools/group-list.js +0 -38
  402. package/dist/tools/tools/group-list.js.map +0 -1
  403. package/dist/tools/tools/group.d.ts +0 -13
  404. package/dist/tools/tools/group.js +0 -34
  405. package/dist/tools/tools/group.js.map +0 -1
  406. package/dist/tools/tools/pagination.d.ts +0 -17
  407. package/dist/tools/tools/pagination.js +0 -28
  408. package/dist/tools/tools/pagination.js.map +0 -1
  409. package/dist/tools/tools/search.d.ts +0 -15
  410. package/dist/tools/tools/search.js +0 -42
  411. package/dist/tools/tools/search.js.map +0 -1
  412. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  413. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  414. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  415. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  416. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  417. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  418. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  419. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  420. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  421. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  422. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  423. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  424. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  425. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -53,15 +53,20 @@ namespace UnityCli
53
53
  protected override Dictionary<string, object> CreateRoot(BridgeRequest request, string digest, object args) { return null; }
54
54
  protected override void OnTick(Dictionary<string, object> root) { }
55
55
  protected override object BuildSuccessData(Dictionary<string, object> root) { return null; }
56
- protected override IEnumerable<(string name, bool destructive)> AdditionalCommands
56
+ protected override IEnumerable<(string name, bool destructive, UnityCliBridgeQueuePolicy.WorkClass queueClass)> AdditionalCommands
57
57
  {
58
- get { yield return (CancelPlanCommand, false); yield return (CancelCommand, true); yield return (CancelStatusCommand, false); }
58
+ get
59
+ {
60
+ yield return (CancelPlanCommand, false, UnityCliBridgeQueuePolicy.WorkClass.Control);
61
+ yield return (CancelCommand, true, UnityCliBridgeQueuePolicy.WorkClass.Control);
62
+ yield return (CancelStatusCommand, false, UnityCliBridgeQueuePolicy.WorkClass.SafeStatus);
63
+ }
59
64
  }
60
65
  internal void Initialize() { InitializeLegacy(); }
61
66
  internal void Shutdown() { ShutdownLegacy(); }
62
67
  internal void BeforeReload() { BeforeReloadLegacy(); }
63
68
  internal override Task<string> Execute(BridgeRequest request, byte[] raw, Func<bool> accept, CancellationToken ct) { return LegacyExecute(request, raw, accept, ct); }
64
- internal override void Tick() { LegacyTick(); }
69
+ internal override void Tick() { if (RetryAdmissionOwnerRecoveryMarker() || RetryCancelAdmissionOwnerMarker()) return; LegacyTick(); }
65
70
  }
66
71
 
67
72
  private sealed class Root { internal string RequestId; internal string RequestDigest; internal string EditorSessionId; internal DateTimeOffset AbsoluteDeadlineUtc; internal Dictionary<string, object> TestOperation; }
@@ -69,6 +74,8 @@ namespace UnityCli
69
74
  {
70
75
  internal string RootRequestId; internal string RootRequestDigest; internal string EditorSessionId; internal DateTimeOffset AbsoluteDeadlineUtc;
71
76
  internal string RunId; internal string CancelRequestId; internal string CancelRequestDigest;
77
+ internal AdmissionOwnerIdentity AdmissionOwner;
78
+ internal BridgeQueueState QueueState;
72
79
  }
73
80
  private enum CancelDispatchOutcome { NotInvoked, Returned, OutcomeUnknown }
74
81
  internal static bool IsCommand(string command) { return operation.IsCommand(command); }
@@ -195,8 +202,9 @@ namespace UnityCli
195
202
  {
196
203
  if (acceptedCancel)
197
204
  {
198
- if (!TryFreezeCancelOutcomeUnknown(root, "The Editor session changed after test cancellation was dispatched; cancellation outcome is unknown.")) return;
199
- Fail(root, "E_EXECUTION_TIMEOUT", "Test cancellation outcome is unknown after the Editor session changed."); return;
205
+ var dispatch = ReadCancelDispatch(root);
206
+ if (dispatch == null || !FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", "Test cancellation outcome is unknown after the Editor session changed.", null, new[] { "Inspect test.run before starting another run or cancellation." }), "outcome_unknown")) return;
207
+ return;
200
208
  }
201
209
  Fail(root, "E_EDITOR_SESSION_REPLACED", "The selected Editor session changed before tests began."); return;
202
210
  }
@@ -207,8 +215,9 @@ namespace UnityCli
207
215
  TickCancel(root);
208
216
  try { root = Load(); } catch { return; }
209
217
  if (root == null) return;
210
- if (!TryFreezeCancelOutcomeUnknown(root, "Test cancellation did not reach a proven terminal before the deadline; outcome is unknown.")) return;
211
- Fail(root, "E_EXECUTION_TIMEOUT", "Test cancellation did not reach a proven terminal before the deadline."); return;
218
+ var dispatch = ReadCancelDispatch(root);
219
+ if (dispatch == null || !FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", "Test cancellation did not reach a proven terminal before the deadline; outcome is unknown.", null, new[] { "Inspect test.run before starting another run or cancellation." }), "outcome_unknown")) return;
220
+ return;
212
221
  }
213
222
  RecoverAfterDeadline(root); return;
214
223
  }
@@ -269,9 +278,9 @@ namespace UnityCli
269
278
  throw new BridgeCommandException("E_REQUEST_ID_REUSED", "Test cancellation identity changed after acceptance.");
270
279
  return CancelAcceptedData(root, runId);
271
280
  }
272
- var dispatch = new CancelDispatchIdentity { RootRequestId = root.RequestId, RootRequestDigest = root.RequestDigest, EditorSessionId = root.EditorSessionId, AbsoluteDeadlineUtc = root.AbsoluteDeadlineUtc, RunId = runId, CancelRequestId = request.RequestId, CancelRequestDigest = cancelDigest };
273
281
  var cancelAdmissionOwner = CaptureAdmissionOwner(request.RequestId);
274
- Update(root, value => { AddAdmissionOwnerFields(value.TestOperation, cancelAdmissionOwner); value.TestOperation["cancelRequested"] = true; value.TestOperation["cancelRequestId"] = dispatch.CancelRequestId; value.TestOperation["cancelRequestDigest"] = dispatch.CancelRequestDigest; value.TestOperation["cancelRootRequestDigest"] = dispatch.RootRequestDigest; value.TestOperation["cancelRunId"] = dispatch.RunId; value.TestOperation["cancelPhaseBefore"] = Text(value.TestOperation, "phase"); value.TestOperation["cancelEffectState"] = "dispatching"; });
282
+ var dispatch = new CancelDispatchIdentity { RootRequestId = root.RequestId, RootRequestDigest = root.RequestDigest, EditorSessionId = root.EditorSessionId, AbsoluteDeadlineUtc = root.AbsoluteDeadlineUtc, RunId = runId, CancelRequestId = request.RequestId, CancelRequestDigest = cancelDigest, AdmissionOwner = cancelAdmissionOwner, QueueState = ProductionQueueState };
283
+ Update(root, value => { AddAdmissionOwnerFields(value.TestOperation, cancelAdmissionOwner); value.TestOperation["cancelRequested"] = true; value.TestOperation["cancelRequestId"] = dispatch.CancelRequestId; value.TestOperation["cancelRequestDigest"] = dispatch.CancelRequestDigest; value.TestOperation["cancelRootRequestDigest"] = dispatch.RootRequestDigest; value.TestOperation["cancelRunId"] = dispatch.RunId; value.TestOperation["cancelPhaseBefore"] = Text(value.TestOperation, "phase"); value.TestOperation["cancelEffectState"] = "dispatching"; value.TestOperation["cancelNeedsContinuation"] = false; });
275
284
  bool isOwned;
276
285
  try
277
286
  {
@@ -282,7 +291,7 @@ namespace UnityCli
282
291
  }
283
292
  catch
284
293
  {
285
- TryStoreCancelDispatchTimeout(dispatch, "Test cancellation dispatch authorization became uncertain before OnRunCancel; cancellation was not invoked.", "outcome_unknown");
294
+ FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", "Test cancellation dispatch authorization became uncertain before OnRunCancel; cancellation was not invoked.", null, new[] { "Inspect test.run before submitting another cancellation." }), "not_invoked");
286
295
  throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test cancellation authorization expired before its effect.", "Inspect test.run before submitting another cancellation.");
287
296
  }
288
297
  string reason;
@@ -292,18 +301,18 @@ namespace UnityCli
292
301
  var message = outcome == CancelDispatchOutcome.OutcomeUnknown
293
302
  ? "Exact Unity Test Runner cancellation may have occurred but its return state is unknown."
294
303
  : "Exact Unity Test Runner cancellation was not invoked: " + reason + ".";
295
- TryStoreCancelDispatchTimeout(dispatch, message, outcome == CancelDispatchOutcome.OutcomeUnknown ? "outcome_unknown" : "not_invoked");
304
+ FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", message, null, new[] { "Inspect test.run state before deciding whether to submit another cancellation." }), outcome == CancelDispatchOutcome.OutcomeUnknown ? "outcome_unknown" : "not_invoked");
296
305
  throw new BridgeCommandException("E_EXECUTION_TIMEOUT", message, "Inspect test.run state before deciding whether to submit another cancellation.");
297
306
  }
298
307
  try
299
308
  {
300
309
  root = Required();
301
310
  if (!MatchesCancelDispatch(root, dispatch)) throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test cancellation root changed after OnRunCancel.");
302
- Update(root, value => value.TestOperation["cancelEffectState"] = "dispatched");
311
+ Update(root, value => { value.TestOperation["cancelEffectState"] = "dispatched"; value.TestOperation["cancelNeedsContinuation"] = true; });
303
312
  }
304
313
  catch
305
314
  {
306
- TryStoreCancelDispatchTimeout(dispatch, "Test cancellation occurred but its dispatched state could not be durably proven.", "outcome_unknown");
315
+ FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", "Test cancellation occurred but its dispatched state could not be durably proven.", null, new[] { "Inspect test.run before submitting another cancellation." }), "outcome_unknown");
307
316
  throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test cancellation outcome is unknown after OnRunCancel.", "Inspect test.run before submitting another cancellation.");
308
317
  }
309
318
  return CancelAcceptedData(root, runId);
@@ -346,9 +355,24 @@ namespace UnityCli
346
355
  private static void TickCancel(Root root)
347
356
  {
348
357
  var effectState = Text(root.TestOperation, "cancelEffectState");
358
+ if (Same(effectState, "cancelled"))
359
+ {
360
+ var dispatch = ReadCancelDispatch(root);
361
+ object terminal;
362
+ if (dispatch != null && TryReadCancelTerminalProof(ProductionQueueState, dispatch.CancelRequestId, dispatch.CancelRequestDigest, dispatch.RootRequestDigest, out terminal))
363
+ FinalizeCancelTerminal(dispatch, terminal, "cancelled");
364
+ return;
365
+ }
349
366
  if (Same(effectState, "dispatching"))
350
367
  {
351
- TryFreezeCancelOutcomeUnknown(root, "Test cancellation return state could not be durably confirmed; the cancellation effect may have occurred.");
368
+ var dispatch = ReadCancelDispatch(root);
369
+ if (dispatch != null) FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", "Test cancellation return state could not be durably confirmed; the cancellation effect may have occurred.", null, new[] { "Inspect test.run before starting another run or cancellation." }), "outcome_unknown");
370
+ return;
371
+ }
372
+ if (Same(effectState, "outcome_unknown"))
373
+ {
374
+ var dispatch = ReadCancelDispatch(root);
375
+ if (dispatch != null) FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", "Test cancellation outcome is unknown after dispatch.", null, new[] { "Inspect test.run before starting another run or cancellation." }), "outcome_unknown");
352
376
  return;
353
377
  }
354
378
  if (!Same(effectState, "dispatched")) return;
@@ -359,71 +383,425 @@ namespace UnityCli
359
383
 
360
384
  /// <summary>校验dispatch后重读root仍与effect前冻结的完整身份和selector一致。</summary>
361
385
  private static bool MatchesCancelDispatch(Root root, CancelDispatchIdentity dispatch)
386
+ {
387
+ return MatchesCancelIdentity(root, dispatch)
388
+ && Same(Text(root.TestOperation, "cancelEffectState"), "dispatching");
389
+ }
390
+
391
+ private static bool MatchesCancelIdentity(Root root, CancelDispatchIdentity dispatch)
362
392
  {
363
393
  return root != null && Same(root.RequestId, dispatch.RootRequestId) && Same(root.RequestDigest, dispatch.RootRequestDigest)
364
394
  && Same(root.EditorSessionId, dispatch.EditorSessionId) && root.AbsoluteDeadlineUtc == dispatch.AbsoluteDeadlineUtc
365
395
  && Same(Text(root.TestOperation, "runId"), dispatch.RunId) && Same(Text(root.TestOperation, "cancelRunId"), dispatch.RunId)
366
396
  && Same(Text(root.TestOperation, "cancelRequestId"), dispatch.CancelRequestId)
367
397
  && Same(Text(root.TestOperation, "cancelRequestDigest"), dispatch.CancelRequestDigest)
368
- && Same(Text(root.TestOperation, "cancelRootRequestDigest"), dispatch.RootRequestDigest)
369
- && Same(Text(root.TestOperation, "cancelEffectState"), "dispatching");
398
+ && Same(Text(root.TestOperation, "cancelRootRequestDigest"), dispatch.RootRequestDigest);
370
399
  }
371
400
 
372
- /// <summary>I/O或身份不确定时直接以冻结selector发布first terminal;root状态只能best-effort推进,不能阻塞terminal。</summary>
373
- private static bool TryStoreCancelDispatchTimeout(CancelDispatchIdentity dispatch, string message, string effectState)
401
+ /// <summary>child terminal与完整恢复marker先于parent Finish持久化,避免父cleanup删除唯一重建身份。</summary>
402
+ private static bool FinalizeCancelTerminal(CancelDispatchIdentity dispatch, object envelope, string effectState)
374
403
  {
375
- try
404
+ if (dispatch == null || dispatch.AdmissionOwner == null) return false;
405
+ var queueState = dispatch.QueueState ?? ProductionQueueState;
406
+ if (!FreezeCancelRecoveryCandidate(queueState, dispatch, envelope, effectState)) return false;
407
+ object first;
408
+ try { first = queueState.StoreTerminalFirst(CancelTerminalScope, dispatch.CancelRequestId, dispatch.CancelRequestDigest, dispatch.RootRequestDigest, envelope); }
409
+ catch { RetainCancelAdmissionForRecovery(dispatch, envelope, effectState); return false; }
410
+ // parent Finish可能同步删除test-run root;此前必须已有完整Recovery/Continuation marker。
411
+ if (!RetainCancelAdmissionForRecovery(dispatch, first, effectState)) return false;
412
+ Root root;
413
+ try { root = Load(queueState); }
414
+ catch { return false; }
415
+ var parentAlreadyFinalized = root == null && CancelParentTerminalMatches(dispatch.RootRequestId, dispatch.RootRequestDigest, effectState, queueState);
416
+ if (!parentAlreadyFinalized && !MatchesCancelIdentity(root, dispatch)) return false;
417
+ if (!parentAlreadyFinalized)
376
418
  {
377
- var terminal = ProtocolEnvelopeFactory.Error(dispatch.CancelRequestId, "E_EXECUTION_TIMEOUT", message, null, new[] { "Inspect the exact test.run business state before starting another run or cancellation." });
378
- UnityCliActiveOperation.StoreTerminalFirst(CancelTerminalScope, dispatch.CancelRequestId, dispatch.CancelRequestDigest, dispatch.RootRequestDigest, terminal);
379
419
  try
380
420
  {
381
- var current = Load();
382
- if (MatchesCancelDispatch(current, dispatch))
421
+ ApplyCancelParentTerminal(root.TestOperation, effectState, first);
422
+ Save(queueState, root);
423
+ FinishCancelParentIfRequired(queueState, root, effectState, first);
424
+ }
425
+ catch
426
+ {
427
+ if (!CancelParentTerminalMatches(dispatch.RootRequestId, dispatch.RootRequestDigest, effectState, queueState)) return false;
428
+ }
429
+ }
430
+ if (!CancelParentTerminalMatches(dispatch.RootRequestId, dispatch.RootRequestDigest, effectState, queueState)
431
+ && (effectState == "cancelled" || effectState == "outcome_unknown")) return false;
432
+ try
433
+ {
434
+ var owner = CaptureAdmissionOwner(queueState, dispatch.CancelRequestId, dispatch.CancelRequestDigest);
435
+ owner.TopRequestDigest = dispatch.RootRequestDigest;
436
+ if (owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.WorkItem
437
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Recovery
438
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Continuation
439
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup) return false;
440
+ var completionMarker = CancelAdmissionMarker(dispatch, owner, first, effectState, false);
441
+ if (owner.OwnerState == UnityCliAdmissionPool.AdmissionOwnerState.WorkItem
442
+ || owner.OwnerState == UnityCliAdmissionPool.AdmissionOwnerState.Recovery)
443
+ owner = PersistAdmissionOwnerTransfer(
444
+ queueState,
445
+ CancelTerminalScope + "-admission-owner",
446
+ completionMarker,
447
+ owner,
448
+ UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup);
449
+ else
450
+ {
451
+ var existingMarker = queueState.LoadOperation(CancelTerminalScope + "-admission-owner");
452
+ if (!CopyCommittedAdmissionOwnerFields(existingMarker, completionMarker, owner, dispatch.RootRequestDigest)) return false;
453
+ queueState.SaveOperation(CancelTerminalScope + "-admission-owner", completionMarker);
454
+ if (!queueState.PersistPool()) return false;
455
+ }
456
+ // Continuation本身是legal terminal owner;completion失败时保留exact marker与intent供Tick重试。
457
+ if (!CompleteAdmissionRoot(queueState, owner)) return false;
458
+ return true;
459
+ }
460
+ catch { return false; }
461
+ }
462
+
463
+ internal static bool FinalizeCancelTerminalForTests(BridgeQueueState state, string rootRequestId, string rootRequestDigest, string runId, string cancelRequestId, string cancelRequestDigest, object envelope, string effectState)
464
+ {
465
+ if (state == null || state.IsolatedExecution == null) throw new ArgumentException("Cancel finalization test state must be isolated.", "state");
466
+ var root = Load(state);
467
+ if (root == null) throw new InvalidOperationException("Cancel finalization test root is missing.");
468
+ var owner = CaptureAdmissionOwner(state, cancelRequestId, cancelRequestDigest);
469
+ return FinalizeCancelTerminal(new CancelDispatchIdentity
470
+ {
471
+ RootRequestId = rootRequestId,
472
+ RootRequestDigest = rootRequestDigest,
473
+ EditorSessionId = root.EditorSessionId,
474
+ AbsoluteDeadlineUtc = root.AbsoluteDeadlineUtc,
475
+ RunId = runId,
476
+ CancelRequestId = cancelRequestId,
477
+ CancelRequestDigest = cancelRequestDigest,
478
+ AdmissionOwner = owner,
479
+ QueueState = state
480
+ }, envelope, effectState);
481
+ }
482
+
483
+ /// <summary>仍存活的WorkItem冻结双持久化失败所需候选;已转移owner无需本地副本。</summary>
484
+ private static bool FreezeCancelRecoveryCandidate(BridgeQueueState state, CancelDispatchIdentity dispatch, object envelope, string effectState)
485
+ {
486
+ if (!CancelTerminalEffectState(effectState) || !CancelTerminalCandidateIsValid(envelope, dispatch.CancelRequestId)) return false;
487
+ BridgeWorkItem item;
488
+ lock (state.QueueLock)
489
+ {
490
+ if (!state.ActiveRequestWork.TryGetValue(dispatch.CancelRequestId, out item)) return true;
491
+ if (!string.Equals(item.RequestDigest, dispatch.CancelRequestDigest, StringComparison.Ordinal)) return false;
492
+ }
493
+ try
494
+ {
495
+ item.FreezeCancelRecoveryCandidate(dispatch.RootRequestId, dispatch.RootRequestDigest, dispatch.RunId, effectState, envelope);
496
+ return true;
497
+ }
498
+ catch { return false; }
499
+ }
500
+
501
+ /// <summary>parent Finish前持久化完整候选并推进Recovery,任何后续失败都禁止queue误完成cancel owner。</summary>
502
+ private static bool RetainCancelAdmissionForRecovery(CancelDispatchIdentity dispatch, object envelope, string effectState)
503
+ {
504
+ try
505
+ {
506
+ var owner = CaptureAdmissionOwner(dispatch.QueueState ?? ProductionQueueState, dispatch.CancelRequestId, dispatch.CancelRequestDigest);
507
+ owner.TopRequestDigest = dispatch.RootRequestDigest;
508
+ if (owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.WorkItem
509
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Recovery
510
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Continuation
511
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup) return false;
512
+ var state = dispatch.QueueState ?? ProductionQueueState;
513
+ var marker = CancelAdmissionMarker(dispatch, owner, envelope, effectState, true);
514
+ if (owner.OwnerState == UnityCliAdmissionPool.AdmissionOwnerState.WorkItem)
515
+ PersistAdmissionOwnerTransfer(state, CancelTerminalScope + "-admission-owner", marker, owner,
516
+ UnityCliAdmissionPool.AdmissionOwnerState.Recovery);
517
+ else
518
+ {
519
+ var existingMarker = state.LoadOperation(CancelTerminalScope + "-admission-owner");
520
+ if (!CopyCommittedAdmissionOwnerFields(existingMarker, marker, owner, dispatch.RootRequestDigest)) return false;
521
+ state.SaveOperation(CancelTerminalScope + "-admission-owner", marker);
522
+ if (!state.PersistPool()) return false;
523
+ }
524
+ return true;
525
+ }
526
+ catch { return false; }
527
+ }
528
+
529
+ private static Dictionary<string, object> CancelAdmissionMarker(CancelDispatchIdentity dispatch, AdmissionOwnerIdentity owner, object envelope, string effectState, bool parentRepairPending)
530
+ {
531
+ if (!CancelTerminalEffectState(effectState) || !CancelTerminalCandidateIsValid(envelope, dispatch.CancelRequestId))
532
+ throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Cancel recovery marker terminal candidate is invalid.");
533
+ return new Dictionary<string, object>
534
+ {
535
+ { "requestId", owner.RequestId }, { "requestDigest", dispatch.RootRequestDigest },
536
+ { "rootRequestId", dispatch.RootRequestId }, { "rootRequestDigest", dispatch.RootRequestDigest },
537
+ { "runId", dispatch.RunId }, { "editorSessionId", dispatch.EditorSessionId },
538
+ { "cancelRequestId", dispatch.CancelRequestId }, { "cancelRequestDigest", dispatch.CancelRequestDigest },
539
+ { "cancelContinuationState", "continuation_pending" }, { "cancelTerminalEnvelope", envelope },
540
+ { "parentRepairEffectState", effectState }, { "parentRepairPending", parentRepairPending }
541
+ };
542
+ }
543
+
544
+ private static void ApplyCancelParentTerminal(Dictionary<string, object> operation, string effectState, object terminal)
545
+ {
546
+ operation["cancelEffectState"] = effectState;
547
+ operation["cancelNeedsContinuation"] = effectState != "not_invoked";
548
+ operation["cancelRequested"] = effectState != "not_invoked";
549
+ operation["cancelTerminalEnvelope"] = terminal;
550
+ if (effectState != "not_invoked") return;
551
+ foreach (var key in new[] { "cancelRequestId", "cancelRequestDigest", "cancelRootRequestDigest", "cancelRunId", "cancelNeedsContinuation", "admissionRootFingerprint", "admissionToken", "admissionSessionId", "admissionListenerId", "admissionListenerGeneration", "admissionExecutionDeadlineMs", "admissionRequestDigest", "admissionOwnerState", "admissionIntendedOwnerState", "admissionTransferCommitted" })
552
+ operation.Remove(key);
553
+ }
554
+
555
+ /// <summary>cancel恢复分别校验父root/lease与cancel terminal;任何mismatch只保留marker,不阻塞整个pool。</summary>
556
+ private static bool RetryCancelAdmissionOwnerMarker()
557
+ {
558
+ return RetryCancelAdmissionOwnerMarker(ProductionQueueState);
559
+ }
560
+
561
+ internal static bool RetryCancelAdmissionOwnerMarkerForTests(BridgeQueueState state)
562
+ {
563
+ if (state == null || state.IsolatedExecution == null) throw new ArgumentException("Cancel recovery test state must be isolated.", "state");
564
+ return RetryCancelAdmissionOwnerMarker(state);
565
+ }
566
+
567
+ private static bool RetryCancelAdmissionOwnerMarker(BridgeQueueState state)
568
+ {
569
+ var markerScope = CancelTerminalScope + "-admission-owner";
570
+ Dictionary<string, object> marker;
571
+ try { marker = state.LoadOperation(markerScope); }
572
+ catch { return true; }
573
+ if (marker == null) return false;
574
+ var cancelRequestId = Text(marker, "requestId");
575
+ var cancelRequestDigest = Text(marker, "admissionRequestDigest");
576
+ var rootRequestId = Text(marker, "rootRequestId");
577
+ var rootRequestDigest = Text(marker, "rootRequestDigest");
578
+ var runId = Text(marker, "runId");
579
+ string continuationState;
580
+ string repairEffectState;
581
+ object candidateEnvelope;
582
+ if (!IsSafeId(cancelRequestId) || !IsSha256(cancelRequestDigest)
583
+ || !IsSafeId(rootRequestId) || !IsSha256(rootRequestDigest) || !Guid.TryParse(runId, out _)
584
+ || !Same(Text(marker, "requestDigest"), rootRequestDigest)
585
+ || !Same(Text(marker, "editorSessionId"), EditorSessionId())
586
+ || !TryReadCancelAdmissionMarkerState(marker, cancelRequestId, cancelRequestDigest, rootRequestDigest, runId,
587
+ out continuationState, out repairEffectState, out _, out candidateEnvelope)) return true;
588
+ AdmissionOwnerIdentity markerOwner;
589
+ try { markerOwner = ReadAdmissionOwnerFields(marker, cancelRequestId); }
590
+ catch { return true; }
591
+ if (!AdmissionMarkerHasImmutableIdentity(marker, markerOwner, rootRequestDigest)) return true;
592
+ markerOwner.TopRequestDigest = rootRequestDigest;
593
+ var pool = state.AdmissionPool;
594
+ if (pool == null) return true;
595
+
596
+ object cancelTerminal = null;
597
+ if (Same(continuationState, "continuation_pending"))
598
+ {
599
+ if (!TryReadCancelTerminalProof(state, cancelRequestId, cancelRequestDigest, rootRequestDigest, out cancelTerminal))
600
+ {
601
+ try
383
602
  {
384
- current.TestOperation["cancelRequested"] = Same(effectState, "outcome_unknown");
385
- current.TestOperation["cancelEffectState"] = effectState;
386
- Save(current);
603
+ state.StoreTerminalFirst(CancelTerminalScope, cancelRequestId, cancelRequestDigest, rootRequestDigest, candidateEnvelope);
387
604
  }
605
+ catch { return true; }
606
+ if (!TryReadCancelTerminalProof(state, cancelRequestId, cancelRequestDigest, rootRequestDigest, out cancelTerminal)) return true;
388
607
  }
389
- catch { }
608
+ if (!RepairCancelParentFromMarker(state, rootRequestId, rootRequestDigest, cancelRequestId,
609
+ cancelRequestDigest, runId, repairEffectState, cancelTerminal)) return true;
610
+ marker["parentRepairPending"] = false;
611
+ }
612
+ else if (!DispatchedCancelParentMatches(state, rootRequestId, rootRequestDigest, cancelRequestId, cancelRequestDigest, runId))
613
+ {
390
614
  return true;
391
615
  }
616
+
617
+ var exact = pool.InspectExactIdentityFields(ToPoolIdentity(markerOwner));
618
+ if (exact == UnityCliAdmissionPool.ExactIdentityOutcome.IdentityMismatchOrBlocked) return true;
619
+ if (exact == UnityCliAdmissionPool.ExactIdentityOutcome.DefinitelyAbsent)
620
+ {
621
+ if (HasAdmissionCompletionIntent(state, markerOwner))
622
+ {
623
+ if (!Same(continuationState, "continuation_pending")) return true;
624
+ try
625
+ {
626
+ if (!state.PersistPool() || !state.ClearMarkers(markerOwner)
627
+ || !ClearAdmissionCompletionIntent(state, markerOwner)) return true;
628
+ return false;
629
+ }
630
+ catch { return true; }
631
+ }
632
+ var intendedRestore = (UnityCliAdmissionPool.AdmissionOwnerState)AdmissionLong(marker, "admissionIntendedOwnerState");
633
+ if (!pool.RestoreConsumed(ToPoolIdentity(markerOwner), intendedRestore, AdmissionBool(marker, "admissionTransferCommitted"))) return true;
634
+ try { if (!state.PersistPool()) return true; }
635
+ catch { return true; }
636
+ }
637
+ AdmissionOwnerIdentity owner;
638
+ try
639
+ {
640
+ owner = CaptureAdmissionOwner(state, cancelRequestId, cancelRequestDigest);
641
+ owner.TopRequestDigest = rootRequestDigest;
642
+ var intended = (UnityCliAdmissionPool.AdmissionOwnerState)AdmissionLong(marker, "admissionIntendedOwnerState");
643
+ if (owner.OwnerState != intended)
644
+ owner = PersistAdmissionOwnerTransfer(state, markerScope, marker, owner, intended);
645
+ else
646
+ {
647
+ marker["admissionTransferCommitted"] = true;
648
+ state.SaveOperation(markerScope, marker);
649
+ if (!state.PersistPool()) return true;
650
+ }
651
+ if (Same(continuationState, "dispatched"))
652
+ return owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Continuation;
653
+ if (owner.OwnerState == UnityCliAdmissionPool.AdmissionOwnerState.WorkItem
654
+ || owner.OwnerState == UnityCliAdmissionPool.AdmissionOwnerState.Recovery)
655
+ owner = PersistAdmissionOwnerTransfer(state, markerScope, marker, owner, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup);
656
+ if (owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Continuation
657
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup) return true;
658
+ return !CompleteAdmissionRoot(state, owner);
659
+ }
660
+ catch { return true; }
661
+ }
662
+
663
+ /// <summary>dispatched marker只恢复owner,不要求尚未生成的terminal;LegacyTick继续观察TestRunner。</summary>
664
+ private static bool DispatchedCancelParentMatches(BridgeQueueState state, string rootRequestId, string rootRequestDigest, string cancelRequestId, string cancelRequestDigest, string runId)
665
+ {
666
+ bool leaseOwned;
667
+ if (!state.TryIsLeaseOwned(rootRequestId, rootRequestDigest, out leaseOwned) || !leaseOwned) return false;
668
+ Root root;
669
+ try { root = Load(state); }
392
670
  catch { return false; }
671
+ return root != null && Same(root.RequestId, rootRequestId) && Same(root.RequestDigest, rootRequestDigest)
672
+ && Same(root.EditorSessionId, EditorSessionId()) && Same(Text(root.TestOperation, "runId"), runId)
673
+ && Same(Text(root.TestOperation, "cancelRunId"), runId)
674
+ && Same(Text(root.TestOperation, "cancelRequestId"), cancelRequestId)
675
+ && Same(Text(root.TestOperation, "cancelRequestDigest"), cancelRequestDigest)
676
+ && Same(Text(root.TestOperation, "cancelRootRequestDigest"), rootRequestDigest)
677
+ && Same(Text(root.TestOperation, "cancelEffectState"), "dispatched")
678
+ && Bool(root.TestOperation, "cancelRequested") && Bool(root.TestOperation, "cancelNeedsContinuation");
393
679
  }
394
680
 
395
- /// <summary>effect后的取消不确定性先按三元identity写first terminal;写失败保留root供下一Tick重试。</summary>
396
- private static bool TryFreezeCancelOutcomeUnknown(Root root, string message)
681
+ private static bool TryReadCancelTerminalProof(BridgeQueueState state, string cancelRequestId, string cancelRequestDigest, string rootRequestDigest, out object terminal)
397
682
  {
398
- var cancelRequestId = Text(root.TestOperation, "cancelRequestId");
399
- var cancelDigest = Text(root.TestOperation, "cancelRequestDigest");
400
- if (!IsSafeId(cancelRequestId) || !IsSha256(cancelDigest) || !Same(Text(root.TestOperation, "cancelRootRequestDigest"), root.RequestDigest))
401
- return false;
683
+ terminal = null;
402
684
  try
403
685
  {
404
- var terminal = ProtocolEnvelopeFactory.Error(cancelRequestId, "E_EXECUTION_TIMEOUT", message, null, new[] { "Inspect the exact test.run business state before starting another run or cancellation." });
405
- UnityCliActiveOperation.StoreTerminalFirst(CancelTerminalScope, cancelRequestId, cancelDigest, root.RequestDigest, terminal);
406
- Update(root, value => value.TestOperation["cancelEffectState"] = "outcome_unknown");
686
+ object exact;
687
+ object selected;
688
+ if (!state.TryGetTerminal(CancelTerminalScope, cancelRequestId, cancelRequestDigest, out exact)
689
+ || !state.TryGetTerminalBySelector(CancelTerminalScope, cancelRequestId, rootRequestDigest, out selected)
690
+ || !string.Equals(UnityCliSerializer.Serialize(exact), UnityCliSerializer.Serialize(selected), StringComparison.Ordinal)) return false;
691
+ terminal = exact;
407
692
  return true;
408
693
  }
409
694
  catch { return false; }
410
695
  }
411
696
 
697
+ /// <summary>cancel terminal存在后先幂等修复父root;重读未证明目标state前绝不释放cancel owner。</summary>
698
+ private static bool RepairCancelParentFromMarker(BridgeQueueState state, string rootRequestId, string rootRequestDigest, string cancelRequestId, string cancelRequestDigest, string runId, string effectState, object cancelTerminal)
699
+ {
700
+ bool parentLeaseOwned;
701
+ if (!state.TryIsLeaseOwned(rootRequestId, rootRequestDigest, out parentLeaseOwned)) return false;
702
+ Root root;
703
+ try { root = Load(state); }
704
+ catch { return false; }
705
+ object parentTerminal;
706
+ var parentTerminalExists = false;
707
+ try { parentTerminalExists = state.TryGetTerminal(Scope, rootRequestId, rootRequestDigest, out parentTerminal); }
708
+ catch { return false; }
709
+ if (root == null) return CancelParentTerminalMatches(rootRequestId, rootRequestDigest, effectState, state);
710
+ if (!Same(root.RequestId, rootRequestId) || !Same(root.RequestDigest, rootRequestDigest)
711
+ || !Same(root.EditorSessionId, EditorSessionId()) || (!parentLeaseOwned && !parentTerminalExists)
712
+ || !Same(Text(root.TestOperation, "runId"), runId)) return false;
713
+ if (CancelParentRepairMatches(root, cancelRequestId, cancelRequestDigest, rootRequestDigest, runId, effectState, cancelTerminal))
714
+ {
715
+ if (effectState != "cancelled" && effectState != "outcome_unknown") return true;
716
+ if (CancelParentTerminalMatches(rootRequestId, rootRequestDigest, effectState, state)) return true;
717
+ try { FinishCancelParentIfRequired(state, root, effectState, cancelTerminal); }
718
+ catch { }
719
+ return CancelParentTerminalMatches(rootRequestId, rootRequestDigest, effectState, state);
720
+ }
721
+ if (!Same(Text(root.TestOperation, "cancelRequestId"), cancelRequestId)
722
+ || !Same(Text(root.TestOperation, "cancelRequestDigest"), cancelRequestDigest)
723
+ || !Same(Text(root.TestOperation, "cancelRootRequestDigest"), rootRequestDigest)
724
+ || !Same(Text(root.TestOperation, "cancelRunId"), runId)) return false;
725
+ try
726
+ {
727
+ ApplyCancelParentTerminal(root.TestOperation, effectState, cancelTerminal);
728
+ Save(state, root);
729
+ FinishCancelParentIfRequired(state, root, effectState, cancelTerminal);
730
+ root = Load(state);
731
+ if (root == null) return CancelParentTerminalMatches(rootRequestId, rootRequestDigest, effectState, state);
732
+ return CancelParentRepairMatches(root, cancelRequestId, cancelRequestDigest, rootRequestDigest, runId, effectState, cancelTerminal)
733
+ && ((effectState != "cancelled" && effectState != "outcome_unknown")
734
+ || CancelParentTerminalMatches(rootRequestId, rootRequestDigest, effectState, state));
735
+ }
736
+ catch { return CancelParentTerminalMatches(rootRequestId, rootRequestDigest, effectState, state); }
737
+ }
738
+
739
+ private static bool CancelParentRepairMatches(Root root, string cancelRequestId, string cancelRequestDigest, string rootRequestDigest, string runId, string effectState, object cancelTerminal)
740
+ {
741
+ object storedTerminal;
742
+ if (root == null || !Same(Text(root.TestOperation, "cancelEffectState"), effectState)
743
+ || Bool(root.TestOperation, "cancelRequested") != (effectState != "not_invoked")
744
+ || !root.TestOperation.TryGetValue("cancelTerminalEnvelope", out storedTerminal)
745
+ || !string.Equals(UnityCliSerializer.Serialize(storedTerminal), UnityCliSerializer.Serialize(cancelTerminal), StringComparison.Ordinal)) return false;
746
+ if (effectState == "not_invoked") return string.IsNullOrEmpty(Text(root.TestOperation, "cancelRequestId"));
747
+ return Same(Text(root.TestOperation, "cancelRequestId"), cancelRequestId)
748
+ && Same(Text(root.TestOperation, "cancelRequestDigest"), cancelRequestDigest)
749
+ && Same(Text(root.TestOperation, "cancelRootRequestDigest"), rootRequestDigest)
750
+ && Same(Text(root.TestOperation, "cancelRunId"), runId);
751
+ }
752
+
753
+ private static void FinishCancelParentIfRequired(BridgeQueueState state, Root root, string effectState, object cancelTerminal)
754
+ {
755
+ if (effectState == "outcome_unknown")
756
+ {
757
+ Finish(state, root, ProtocolEnvelopeFactory.Error(root.RequestId, "E_EXECUTION_TIMEOUT", "Test cancellation may have occurred but its exact effect could not be proven.", null, new[] { "Inspect the Unity Test Runner before submitting another mutation." }));
758
+ return;
759
+ }
760
+ if (effectState == "cancelled")
761
+ Finish(state, root, ProtocolEnvelopeFactory.Error(root.RequestId, "E_TEST_RUN_CANCELLED", "The exact tracked Unity test run was cancelled.", CancelTerminalData(cancelTerminal), new[] { "Inspect the test.cancel proof before starting another test run." }));
762
+ }
763
+
764
+ private static bool CancelParentTerminalMatches(string rootRequestId, string rootRequestDigest, string effectState, BridgeQueueState state)
765
+ {
766
+ if (effectState != "cancelled" && effectState != "outcome_unknown") return true;
767
+ try
768
+ {
769
+ object terminal;
770
+ if (!state.TryGetTerminal(Scope, rootRequestId, rootRequestDigest, out terminal)) return false;
771
+ var parsed = UnityCliSerializer.Parse(UnityCliSerializer.Serialize(terminal)) as Dictionary<string, object>;
772
+ object okValue;
773
+ object errorValue;
774
+ var error = parsed != null && parsed.TryGetValue("error", out errorValue) ? errorValue as Dictionary<string, object> : null;
775
+ return parsed != null && parsed.TryGetValue("ok", out okValue) && okValue is bool && !(bool)okValue
776
+ && error != null && Same(Text(error, "code"), effectState == "cancelled" ? "E_TEST_RUN_CANCELLED" : "E_EXECUTION_TIMEOUT");
777
+ }
778
+ catch { return false; }
779
+ }
780
+
781
+ private static CancelDispatchIdentity ReadCancelDispatch(Root root)
782
+ {
783
+ if (root == null || root.TestOperation == null) return null;
784
+ var id = Text(root.TestOperation, "cancelRequestId");
785
+ var digest = Text(root.TestOperation, "cancelRequestDigest");
786
+ var runId = Text(root.TestOperation, "cancelRunId");
787
+ if (!IsSafeId(id) || !IsSha256(digest) || !Guid.TryParse(runId, out _)
788
+ || !Same(Text(root.TestOperation, "cancelRootRequestDigest"), root.RequestDigest)) return null;
789
+ AdmissionOwnerIdentity owner;
790
+ try { owner = CaptureAdmissionOwner(id, digest); } catch { return null; }
791
+ return new CancelDispatchIdentity { RootRequestId = root.RequestId, RootRequestDigest = root.RequestDigest, EditorSessionId = root.EditorSessionId, AbsoluteDeadlineUtc = root.AbsoluteDeadlineUtc, RunId = runId, CancelRequestId = id, CancelRequestDigest = digest, AdmissionOwner = owner };
792
+ }
793
+
412
794
  private static void FinishCancelled(Root root)
413
795
  {
414
796
  string[] ids; string reason;
415
797
  if (!TryReadActiveRunIds(out ids, out reason)) ids = new string[0];
416
798
  var data = CancelData(root, true, false, ids);
417
- cancelTerminalRequestId = Text(root.TestOperation, "cancelRequestId");
418
- var cancelDigest = Text(root.TestOperation, "cancelRequestDigest");
419
- if (!IsSafeId(cancelTerminalRequestId) || !IsSha256(cancelDigest)
420
- || !Same(Text(root.TestOperation, "cancelRootRequestDigest"), root.RequestDigest))
421
- throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test cancellation terminal identity is incomplete; outcome is unknown.");
422
- var cancelEnvelope = ProtocolEnvelopeFactory.Success(cancelTerminalRequestId, data);
423
- UnityCliActiveOperation.StoreTerminalFirst(CancelTerminalScope, cancelTerminalRequestId, cancelDigest, root.RequestDigest, cancelEnvelope);
424
- CompleteAdmissionRoot(ReadAdmissionOwnerFields(root.TestOperation, cancelTerminalRequestId));
799
+ var dispatch = ReadCancelDispatch(root);
800
+ if (dispatch == null) throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test cancellation terminal identity is incomplete; outcome is unknown.");
801
+ if (!FinalizeCancelTerminal(dispatch, ProtocolEnvelopeFactory.Success(dispatch.CancelRequestId, data), "cancelled"))
802
+ throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test cancellation continuation owner could not be finalized.");
803
+ cancelTerminalRequestId = dispatch.CancelRequestId;
425
804
  cancelTerminalData = data;
426
- Finish(root, ProtocolEnvelopeFactory.Error(root.RequestId, "E_TEST_RUN_CANCELLED", "The exact tracked Unity test run was cancelled.", data, new[] { "Inspect the test.cancel proof before starting another test run." }));
427
805
  }
428
806
 
429
807
  private static Dictionary<string, object> CancelAcceptedData(Root root, string runId)
@@ -511,13 +889,15 @@ namespace UnityCli
511
889
  private static object ResultEnvelope(Root root, ITestResultAdaptor result) { var data = CaptureResultData(root, result); object artifactFailed; if (data.TryGetValue("artifactWriteFailed", out artifactFailed) && artifactFailed is bool && (bool)artifactFailed) { data.Remove("artifactWriteFailed"); return ProtocolEnvelopeFactory.Error(root.RequestId, "E_TEST_ARTIFACT_WRITE_FAILED", "RunFinished was proven but its NUnit XML artifact could not be written.", data, new[] { "Inspect the returned test summary and verify the UnityCli data directory is writable." }); } var failed = result.FailCount; var count = result.PassCount + result.FailCount + result.SkipCount + result.InconclusiveCount; return count == 0 ? (object)ProtocolEnvelopeFactory.Error(root.RequestId, "E_TESTS_NOT_FOUND", "The requested filters matched no Unity tests.", data, new[] { "Inspect the available test names and relax the test filters." }) : failed > 0 ? (object)ProtocolEnvelopeFactory.Error(root.RequestId, "E_TESTS_FAILED", "One or more Unity tests failed.", data, new[] { "Inspect failures and the optional NUnit XML artifact." }) : ProtocolEnvelopeFactory.Success(root.RequestId, data); }
512
890
  private static Dictionary<string, object> CaptureResultData(Root root, ITestResultAdaptor result) { var summary = new Dictionary<string, object> { { "platform", Text(root.TestOperation, "platform") }, { "status", result.FailCount == 0 ? "passed" : "failed" }, { "testCount", (long)(result.PassCount + result.FailCount + result.SkipCount + result.InconclusiveCount) }, { "passed", (long)result.PassCount }, { "failed", (long)result.FailCount }, { "skipped", (long)result.SkipCount }, { "inconclusive", (long)result.InconclusiveCount }, { "duration", result.Duration }, { "startedAt", result.StartTime.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) }, { "finishedAt", result.EndTime.ToUniversalTime().ToString("O", CultureInfo.InvariantCulture) } }; var data = new Dictionary<string, object> { { "summary", summary }, { "failures", Failures(result).Cast<object>().ToList() } }; if (Bool(root.TestOperation, "xml")) { try { data["xml"] = WriteXml(root, result); } catch { data["artifactWriteFailed"] = true; } } return data; }
513
891
  private static Root Required() { var root = Load(); if (root == null) throw new BridgeCommandException("E_REQUEST_NOT_FOUND", "Test active operation was not found."); return root; }
514
- private static Root Load()
892
+ private static Root Load() { return Load(ProductionQueueState); }
893
+ private static Root Load(BridgeQueueState state)
515
894
  {
516
- try { var state = UnityCliActiveOperation.Load(Scope); return state == null ? null : Parse(state); }
895
+ try { var value = state.LoadOperation(Scope); return value == null ? null : Parse(value); }
517
896
  catch (BridgeCommandException) { throw; }
518
897
  catch (Exception exception) { throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Test active state cannot be read; outcome is unknown: " + exception.GetType().Name + "."); }
519
898
  }
520
- private static void Save(Root root) { UnityCliActiveOperation.Save(Scope, Data(root)); }
899
+ private static void Save(Root root) { Save(ProductionQueueState, root); }
900
+ private static void Save(BridgeQueueState state, Root root) { state.SaveOperation(Scope, Data(root)); }
521
901
  private static void Update(Root root, Action<Root> change)
522
902
  {
523
903
  var current = Required();
@@ -527,17 +907,23 @@ namespace UnityCli
527
907
  change(current);
528
908
  Save(current);
529
909
  }
530
- private static void Finish(Root root, object envelope)
910
+ private static void Finish(Root root, object envelope) { Finish(ProductionQueueState, root, envelope); }
911
+ private static void Finish(BridgeQueueState state, Root root, object envelope)
531
912
  {
532
913
  bool isOwned;
533
- if (!UnityCliSessionMutationLease.TryIsOwnedBy(root.RequestId, root.RequestDigest, out isOwned) || !isOwned)
914
+ if (!state.TryIsLeaseOwned(root.RequestId, root.RequestDigest, out isOwned) || !isOwned)
534
915
  envelope = ProtocolEnvelopeFactory.Error(root.RequestId, "E_EXECUTION_TIMEOUT", "Test run lost its exact mutation lease after effect; outcome is unknown.", null, new[] { "Inspect the Unity Test Runner before submitting another mutation." });
535
916
  root.TestOperation["phase"] = "terminal_pending";
536
917
  root.TestOperation["terminalEnvelope"] = envelope;
537
- Save(root);
538
- var first = UnityCliActiveOperation.StoreTerminalFirst(Scope, root.RequestId, root.RequestDigest, envelope);
539
- terminalRequestId = root.RequestId; terminalRequestDigest = root.RequestDigest; terminalEnvelope = first;
540
- UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, root.RequestId, root.RequestDigest);
918
+ Save(state, root);
919
+ var first = state.StoreTerminalFirst(Scope, root.RequestId, root.RequestDigest, envelope);
920
+ if (object.ReferenceEquals(state, ProductionQueueState))
921
+ {
922
+ terminalRequestId = root.RequestId;
923
+ terminalRequestDigest = root.RequestDigest;
924
+ terminalEnvelope = first;
925
+ }
926
+ UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, root.RequestId, root.RequestDigest, state);
541
927
  }
542
928
  private static void RecoverTerminalCleanup()
543
929
  {