@alpsckr/unitycli 0.4.2 → 0.4.4

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 (420) 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 +122 -34
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/types.d.ts +6 -5
  51. package/dist/bridge/types.js +16 -2
  52. package/dist/bridge/types.js.map +1 -1
  53. package/dist/catalog-protocol.d.ts +64 -0
  54. package/dist/catalog-protocol.js +460 -0
  55. package/dist/catalog-protocol.js.map +1 -0
  56. package/dist/cli-commands/command.js +84 -61
  57. package/dist/cli-commands/command.js.map +1 -1
  58. package/dist/cli-commands/extensions.js +70 -50
  59. package/dist/cli-commands/extensions.js.map +1 -1
  60. package/dist/cli-commands/instances.js +1 -1
  61. package/dist/cli-commands/instances.js.map +1 -1
  62. package/dist/cli-process-owners.d.ts +6 -0
  63. package/dist/cli-process-owners.js +27 -0
  64. package/dist/cli-process-owners.js.map +1 -0
  65. package/dist/cli.d.ts +1 -1
  66. package/dist/cli.js +20 -5
  67. package/dist/cli.js.map +1 -1
  68. package/dist/contracts/command-contract.js +14 -4
  69. package/dist/contracts/command-contract.js.map +1 -1
  70. package/dist/doctor.d.ts +4 -6
  71. package/dist/doctor.js +429 -398
  72. package/dist/doctor.js.map +1 -1
  73. package/dist/editor-blocker-observer.d.ts +2 -1
  74. package/dist/editor-blocker-observer.js +40 -13
  75. package/dist/editor-blocker-observer.js.map +1 -1
  76. package/dist/extension-availability.d.ts +27 -9
  77. package/dist/extension-availability.js +96 -56
  78. package/dist/extension-availability.js.map +1 -1
  79. package/dist/extensions/descriptor-loader.d.ts +2 -2
  80. package/dist/extensions/descriptor-loader.js +3 -4
  81. package/dist/extensions/descriptor-loader.js.map +1 -1
  82. package/dist/extensions/index.d.ts +1 -1
  83. package/dist/extensions/index.js +1 -1
  84. package/dist/extensions/index.js.map +1 -1
  85. package/dist/extensions/installer.d.ts +24 -7
  86. package/dist/extensions/installer.js +142 -66
  87. package/dist/extensions/installer.js.map +1 -1
  88. package/dist/extensions/registry.js +3 -5
  89. package/dist/extensions/registry.js.map +1 -1
  90. package/dist/extensions/state-contract.d.ts +11 -0
  91. package/dist/extensions/state-contract.js +50 -0
  92. package/dist/extensions/state-contract.js.map +1 -0
  93. package/dist/instances.d.ts +13 -1
  94. package/dist/instances.js +26 -0
  95. package/dist/instances.js.map +1 -1
  96. package/dist/package-manager.d.ts +0 -24
  97. package/dist/package-manager.js +5 -58
  98. package/dist/package-manager.js.map +1 -1
  99. package/dist/project-tool-discovery.d.ts +5 -2
  100. package/dist/project-tool-discovery.js +22 -18
  101. package/dist/project-tool-discovery.js.map +1 -1
  102. package/dist/runtime-code.js +1 -1
  103. package/dist/runtime-code.js.map +1 -1
  104. package/dist/tool-name.d.ts +4 -0
  105. package/dist/tool-name.js +15 -0
  106. package/dist/tool-name.js.map +1 -0
  107. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  108. package/dist/tools/.generated-completion-registry.js +763 -325
  109. package/dist/tools/.generated-completion-registry.js.map +1 -1
  110. package/dist/tools/.generated-registry.d.ts +6 -11
  111. package/dist/tools/.generated-registry.js +6 -11
  112. package/dist/tools/.generated-registry.js.map +1 -1
  113. package/dist/tools/.generated-schemas.js +11 -16
  114. package/dist/tools/.generated-schemas.js.map +1 -1
  115. package/dist/tools/asset/refresh-status.d.ts +3 -0
  116. package/dist/tools/asset/refresh-status.js +12 -0
  117. package/dist/tools/asset/refresh-status.js.map +1 -0
  118. package/dist/tools/asset/refresh.js +7 -3
  119. package/dist/tools/asset/refresh.js.map +1 -1
  120. package/dist/tools/build/player-status.d.ts +3 -0
  121. package/dist/tools/build/player-status.js +12 -0
  122. package/dist/tools/build/player-status.js.map +1 -0
  123. package/dist/tools/build/run.js +1 -1
  124. package/dist/tools/build/run.js.map +1 -1
  125. package/dist/tools/catalog-control.d.ts +22 -0
  126. package/dist/tools/catalog-control.js +335 -0
  127. package/dist/tools/catalog-control.js.map +1 -0
  128. package/dist/tools/catalog.d.ts +7 -9
  129. package/dist/tools/catalog.js +66 -45
  130. package/dist/tools/catalog.js.map +1 -1
  131. package/dist/tools/commands.d.ts +1 -1
  132. package/dist/tools/commands.js +44 -11
  133. package/dist/tools/commands.js.map +1 -1
  134. package/dist/tools/define.js +3 -2
  135. package/dist/tools/define.js.map +1 -1
  136. package/dist/tools/dispatcher.d.ts +53 -6
  137. package/dist/tools/dispatcher.js +277 -108
  138. package/dist/tools/dispatcher.js.map +1 -1
  139. package/dist/tools/editor/compile.js +1 -1
  140. package/dist/tools/editor/compile.js.map +1 -1
  141. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  142. package/dist/tools/editor/play-mode-status.js +12 -0
  143. package/dist/tools/editor/play-mode-status.js.map +1 -0
  144. package/dist/tools/editor/readiness.d.ts +0 -1
  145. package/dist/tools/editor/readiness.js +0 -1
  146. package/dist/tools/editor/readiness.js.map +1 -1
  147. package/dist/tools/editor/refresh.d.ts +7 -3
  148. package/dist/tools/editor/refresh.js +20 -15
  149. package/dist/tools/editor/refresh.js.map +1 -1
  150. package/dist/tools/editor/state.d.ts +2 -2
  151. package/dist/tools/editor/state.js +13 -12
  152. package/dist/tools/editor/state.js.map +1 -1
  153. package/dist/tools/metadata/derived.js +1 -7
  154. package/dist/tools/metadata/derived.js.map +1 -1
  155. package/dist/tools/metadata/status.js +1 -12
  156. package/dist/tools/metadata/status.js.map +1 -1
  157. package/dist/tools/metadata/types.d.ts +0 -2
  158. package/dist/tools/navmesh/live.js +1 -1
  159. package/dist/tools/navmesh/live.js.map +1 -1
  160. package/dist/tools/occlusion/live.js +1 -1
  161. package/dist/tools/occlusion/live.js.map +1 -1
  162. package/dist/tools/package/deploy.js +2 -2
  163. package/dist/tools/package/deploy.js.map +1 -1
  164. package/dist/tools/package/embed.js +1 -1
  165. package/dist/tools/package/embed.js.map +1 -1
  166. package/dist/tools/package/install-status.d.ts +3 -0
  167. package/dist/tools/package/install-status.js +12 -0
  168. package/dist/tools/package/install-status.js.map +1 -0
  169. package/dist/tools/package/install.js +3 -3
  170. package/dist/tools/package/install.js.map +1 -1
  171. package/dist/tools/package/live-resolve.js +45 -12
  172. package/dist/tools/package/live-resolve.js.map +1 -1
  173. package/dist/tools/package/registry-clear.js.map +1 -1
  174. package/dist/tools/package/registry-set.js.map +1 -1
  175. package/dist/tools/package/remove-status.d.ts +3 -0
  176. package/dist/tools/package/remove-status.js +12 -0
  177. package/dist/tools/package/remove-status.js.map +1 -0
  178. package/dist/tools/package/remove.js +3 -3
  179. package/dist/tools/package/remove.js.map +1 -1
  180. package/dist/tools/package/resolve.js +3 -3
  181. package/dist/tools/package/resolve.js.map +1 -1
  182. package/dist/tools/package/restore.js +2 -2
  183. package/dist/tools/package/restore.js.map +1 -1
  184. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  185. package/dist/tools/status/support.d.ts +13 -0
  186. package/dist/tools/status/support.js +28 -0
  187. package/dist/tools/status/support.js.map +1 -0
  188. package/dist/tools/test/cancel.js +2 -2
  189. package/dist/tools/test/cancel.js.map +1 -1
  190. package/dist/tools/test/run-status.d.ts +3 -0
  191. package/dist/tools/test/run-status.js +12 -0
  192. package/dist/tools/test/run-status.js.map +1 -0
  193. package/dist/tools/test/run.js +7 -4
  194. package/dist/tools/test/run.js.map +1 -1
  195. package/dist/tools/tools/group-summaries.js +10 -14
  196. package/dist/tools/tools/group-summaries.js.map +1 -1
  197. package/docs/architecture.md +4 -4
  198. package/docs/availability-differences.md +4 -4
  199. package/docs/command-execution.md +4 -3
  200. package/docs/error-codes.md +13 -5
  201. package/docs/extensions.md +1 -7
  202. package/docs/risk-and-side-effects.md +4 -3
  203. package/docs/safety-boundaries.md +3 -1
  204. package/docs/target-and-instance.md +1 -1
  205. package/docs/tool-discovery.md +17 -10
  206. package/docs/tool-execution.md +18 -7
  207. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  208. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  209. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  210. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  211. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  212. package/package.json +2 -4
  213. package/skills/unitycli/SKILL.md +35 -42
  214. package/skills/unitycli/manifest.json +1 -1
  215. package/skills/unitycli/references/operation-protocol.md +3 -3
  216. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  217. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  218. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  219. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  220. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  221. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  222. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  223. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  224. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  225. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  226. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  227. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  228. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  229. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  230. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  231. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  232. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  233. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  234. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  235. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  236. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  237. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  238. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  239. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  240. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  241. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  242. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  244. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  245. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  246. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  247. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  248. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  249. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  250. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  251. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  252. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  253. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  254. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  255. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  256. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  257. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  258. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  259. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  260. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  261. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  262. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  263. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  264. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  265. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  266. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  267. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  268. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  269. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  270. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  271. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  272. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  273. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  274. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  275. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  276. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  277. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  278. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  279. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  280. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  281. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  282. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  283. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  284. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  285. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  286. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  287. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  288. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  289. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  290. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  291. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  292. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  293. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  294. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  295. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  296. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  297. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  298. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  299. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  300. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  301. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  302. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  303. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  304. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  305. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  306. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  307. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  308. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  309. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  310. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  311. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  312. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  313. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  314. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  315. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  316. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  317. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  318. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  319. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  320. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  321. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  322. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  323. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  324. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  325. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  326. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  327. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  328. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  329. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  330. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  331. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  332. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  333. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  334. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  335. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  336. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  337. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  338. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  339. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  340. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  341. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  342. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  343. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  344. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  345. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  346. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  347. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  348. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  349. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  350. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  351. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  352. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  353. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  354. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  355. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  356. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  357. package/unitypkg/package.json +1 -1
  358. package/contracts/static-capability-catalog.json +0 -48226
  359. package/dist/bridge/domains/diagnose.d.ts +0 -2
  360. package/dist/bridge/domains/diagnose.js +0 -11
  361. package/dist/bridge/domains/diagnose.js.map +0 -1
  362. package/dist/bridge/domains/extension.d.ts +0 -8
  363. package/dist/bridge/domains/extension.js +0 -20
  364. package/dist/bridge/domains/extension.js.map +0 -1
  365. package/dist/capabilities.d.ts +0 -5
  366. package/dist/capabilities.js +0 -84
  367. package/dist/capabilities.js.map +0 -1
  368. package/dist/static-capability-catalog.d.ts +0 -13
  369. package/dist/static-capability-catalog.js +0 -41
  370. package/dist/static-capability-catalog.js.map +0 -1
  371. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  372. package/dist/tools/diagnose/capabilities.js +0 -34
  373. package/dist/tools/diagnose/capabilities.js.map +0 -1
  374. package/dist/tools/package/info.d.ts +0 -19
  375. package/dist/tools/package/info.js +0 -27
  376. package/dist/tools/package/info.js.map +0 -1
  377. package/dist/tools/package/list.d.ts +0 -27
  378. package/dist/tools/package/list.js +0 -21
  379. package/dist/tools/package/list.js.map +0 -1
  380. package/dist/tools/package/registry-get.d.ts +0 -10
  381. package/dist/tools/package/registry-get.js +0 -22
  382. package/dist/tools/package/registry-get.js.map +0 -1
  383. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  384. package/dist/tools/package/scoped-registry-list.js +0 -22
  385. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  386. package/dist/tools/package/search.d.ts +0 -17
  387. package/dist/tools/package/search.js +0 -39
  388. package/dist/tools/package/search.js.map +0 -1
  389. package/dist/tools/package/status.d.ts +0 -21
  390. package/dist/tools/package/status.js +0 -37
  391. package/dist/tools/package/status.js.map +0 -1
  392. package/dist/tools/tools/describe.d.ts +0 -27
  393. package/dist/tools/tools/describe.js +0 -35
  394. package/dist/tools/tools/describe.js.map +0 -1
  395. package/dist/tools/tools/group-list.d.ts +0 -16
  396. package/dist/tools/tools/group-list.js +0 -38
  397. package/dist/tools/tools/group-list.js.map +0 -1
  398. package/dist/tools/tools/group.d.ts +0 -13
  399. package/dist/tools/tools/group.js +0 -34
  400. package/dist/tools/tools/group.js.map +0 -1
  401. package/dist/tools/tools/pagination.d.ts +0 -17
  402. package/dist/tools/tools/pagination.js +0 -28
  403. package/dist/tools/tools/pagination.js.map +0 -1
  404. package/dist/tools/tools/search.d.ts +0 -15
  405. package/dist/tools/tools/search.js +0 -42
  406. package/dist/tools/tools/search.js.map +0 -1
  407. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  408. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  409. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  410. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  411. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  412. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  413. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  414. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  415. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  416. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  417. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  418. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  419. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  420. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -1,5 +1,6 @@
1
1
  using System;
2
2
  using System.Collections.Concurrent;
3
+ using System.Collections.Generic;
3
4
  using System.Linq;
4
5
  using System.Threading.Tasks;
5
6
  using NUnit.Framework;
@@ -33,63 +34,153 @@ namespace UnityCli.Tests
33
34
 
34
35
  Assert.That(pool.Revoke(Request(0), Fingerprint(0), grants[0].Token), Is.True);
35
36
  Assert.That(pool.Capture().Active, Is.EqualTo(4));
36
- Assert.That(pool.Capture().GateClosed, Is.True);
37
37
  Assert.That(pool.Acquire(Request(5), Fingerprint(5), ListenerId, 1).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.Closed));
38
-
39
38
  Assert.That(pool.Revoke(Request(1), Fingerprint(1), grants[1].Token), Is.True);
40
39
  Assert.That(pool.Capture().Active, Is.EqualTo(3));
41
40
  Assert.That(pool.Capture().GateClosed, Is.False);
42
41
  Assert.That(pool.Acquire(Request(5), Fingerprint(5), ListenerId, 1).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.Granted));
43
42
  Assert.That(pool.Acquire(Request(6), Fingerprint(6), ListenerId, 1).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.Granted));
44
43
  Assert.That(pool.Capture().Active, Is.EqualTo(5));
45
- Assert.That(pool.Capture().GateClosed, Is.True);
46
44
  }
47
45
 
48
46
  [Test]
49
- public void SameIdentityAttachesWithoutCountingAndDifferentFingerprintFailsClosed()
47
+ public void GrantedHasOnlyAuthorizeOrRevokeExit()
50
48
  {
51
49
  var pool = Pool();
52
- var first = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
53
- var attached = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
54
- var conflict = pool.Acquire(Request(0), Fingerprint(1), ListenerId, 1);
55
- Assert.That(attached.Token, Is.EqualTo(first.Token));
56
- Assert.That(conflict.Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.IdentityMismatch));
57
- Assert.That(pool.Capture().Active, Is.EqualTo(1));
50
+ var revoked = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
51
+ Assert.That(pool.Revoke(Request(0), Fingerprint(0), revoked.Token), Is.True);
52
+ Assert.That(pool.Capture().Active, Is.EqualTo(0));
53
+
54
+ var granted = pool.Acquire(Request(1), Fingerprint(1), ListenerId, 1);
55
+ var consumed = pool.AuthorizeAndCapture(Request(1), Fingerprint(1), Digest(1), granted.Token, ListenerId, 1, 5000);
56
+ Assert.That(consumed.Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
57
+ Assert.That(consumed.Identity.OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem));
58
+ Assert.That(pool.Revoke(Request(1), Fingerprint(1), granted.Token), Is.False);
58
59
  }
59
60
 
60
61
  [Test]
61
- public void TokenIs256BitConsumedOnceAndOldGenerationIsAtomicallyRevoked()
62
+ public void AuthorizeAndCaptureIsAtomicAndCompensationIsExactAndIdempotent()
62
63
  {
63
- long now = 1000;
64
- var pool = Pool(() => now);
65
- var stale = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 7);
66
- Assert.That(stale.Token, Has.Length.EqualTo(64));
67
- Assert.That(stale.Token.All(Uri.IsHexDigit), Is.True);
68
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), stale.Token, ListenerId, 8, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.IdentityMismatch));
64
+ var pool = Pool();
65
+ var grant = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
66
+ var result = pool.AuthorizeAndCapture(Request(0), Fingerprint(0), Digest(0), grant.Token, ListenerId, 1, 5000);
67
+ Assert.That(result.Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
68
+ Assert.That(result.NewlyConsumed, Is.True);
69
+ Assert.That(result.Identity.OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem));
70
+ var replay = pool.AuthorizeAndCapture(Request(0), Fingerprint(0), Digest(0), grant.Token, ListenerId, 1, 5000);
71
+ Assert.That(replay.Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.AlreadyConsumed));
72
+ Assert.That(replay.Identity.RequestDigest, Is.EqualTo(result.Identity.RequestDigest));
73
+ Assert.That(replay.Identity.Token, Is.EqualTo(result.Identity.Token));
74
+ Assert.That(replay.Identity.OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem));
75
+ Assert.That(pool.AuthorizeAndCapture(Request(0), Fingerprint(0), Digest(9), grant.Token, ListenerId, 1, 5000).Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.IdentityMismatch));
76
+ Assert.That(pool.CompensateAuthorization(result.Identity), Is.True);
77
+ Assert.That(pool.CompensateAuthorization(result.Identity), Is.True);
69
78
  Assert.That(pool.Capture().Active, Is.EqualTo(0));
70
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), stale.Token, ListenerId, 7, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Missing));
71
-
72
- var grant = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 8);
73
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), grant.Token, ListenerId, 8, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
74
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), grant.Token, ListenerId, 8, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.AlreadyConsumed));
75
- var consumed = pool.Capture().Records.Single();
76
- Assert.That(consumed.State, Is.EqualTo(UnityCliAdmissionPool.AdmissionState.Consumed));
77
- Assert.That(consumed.InstanceId, Is.EqualTo(InstanceId));
78
- Assert.That(consumed.SessionId, Is.EqualTo(SessionId));
79
- Assert.That(pool.AttachConsumed(Request(0), Fingerprint(0), ListenerId, 8, 5000), Is.True);
80
- Assert.That(pool.AttachConsumed(Request(0), Fingerprint(0), "listener-new", 9, 5000), Is.False);
81
- Assert.That(pool.RecoverConsumed(Request(0), Fingerprint(0), "listener-new", 9, 5000), Is.True);
82
- Assert.That(pool.AttachConsumed(Request(0), Fingerprint(0), "listener-new", 9, 5000), Is.True);
79
+
80
+ var successor = Consume(pool, 0);
81
+ Assert.That(pool.CompensateAuthorization(result.Identity), Is.False);
83
82
  Assert.That(pool.Capture().Active, Is.EqualTo(1));
83
+ Assert.That(pool.CompleteTrusted(successor), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted));
84
84
  }
85
85
 
86
86
  [Test]
87
- public void GrantTtlUsesMonotonicClockIndependentlyFromUtcDeadline()
87
+ public void FullOwnerTransitionTableRejectsEveryIllegalEdge()
88
+ {
89
+ var states = new[]
90
+ {
91
+ UnityCliAdmissionPool.AdmissionOwnerState.WorkItem,
92
+ UnityCliAdmissionPool.AdmissionOwnerState.Coordinator,
93
+ UnityCliAdmissionPool.AdmissionOwnerState.Continuation,
94
+ UnityCliAdmissionPool.AdmissionOwnerState.Recovery,
95
+ UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup
96
+ };
97
+ var legal = new HashSet<string>(StringComparer.Ordinal)
98
+ {
99
+ Edge(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator),
100
+ Edge(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem, UnityCliAdmissionPool.AdmissionOwnerState.Continuation),
101
+ Edge(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem, UnityCliAdmissionPool.AdmissionOwnerState.Recovery),
102
+ Edge(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup),
103
+ Edge(UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup),
104
+ Edge(UnityCliAdmissionPool.AdmissionOwnerState.Recovery, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup)
105
+ };
106
+
107
+ foreach (var source in states)
108
+ foreach (var target in states)
109
+ {
110
+ var pool = Pool();
111
+ var current = OwnerAt(pool, source);
112
+ var expected = source == target || legal.Contains(Edge(source, target));
113
+ Assert.That(pool.PrepareOwnerTransfer(current, target), Is.EqualTo(expected), Edge(source, target));
114
+ if (!expected || source == target) continue;
115
+ UnityCliAdmissionPool.ConsumedIdentity successor;
116
+ Assert.That(pool.CommitOwnerTransfer(current, target, out successor), Is.True, Edge(source, target));
117
+ Assert.That(successor.OwnerState, Is.EqualTo(target));
118
+ Assert.That(pool.PrepareOwnerTransfer(successor, target), Is.True, "idempotent prepare " + Edge(source, target));
119
+ UnityCliAdmissionPool.ConsumedIdentity repeated;
120
+ Assert.That(pool.CommitOwnerTransfer(successor, target, out repeated), Is.True, "idempotent commit " + Edge(source, target));
121
+ Assert.That(repeated.OwnerState, Is.EqualTo(target));
122
+ }
123
+ }
124
+
125
+ [Test]
126
+ public void OnlyWorkItemContinuationAndTerminalCleanupCanComplete()
127
+ {
128
+ foreach (var state in new[] { UnityCliAdmissionPool.AdmissionOwnerState.WorkItem, UnityCliAdmissionPool.AdmissionOwnerState.Continuation, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup })
129
+ {
130
+ var pool = Pool();
131
+ var owner = OwnerAt(pool, state);
132
+ Assert.That(pool.CompleteTrusted(owner), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted), state.ToString());
133
+ Assert.That(pool.CompleteTrusted(owner), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.DefinitelyAbsent), "idempotent " + state);
134
+ }
135
+ foreach (var state in new[] { UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, UnityCliAdmissionPool.AdmissionOwnerState.Recovery })
136
+ {
137
+ var pool = Pool();
138
+ Assert.That(pool.CompleteTrusted(OwnerAt(pool, state)), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.IdentityMismatchOrBlocked), state.ToString());
139
+ Assert.That(pool.Capture().Active, Is.EqualTo(1));
140
+ }
141
+ }
142
+
143
+ [Test]
144
+ public void TransferAndCompletionRequireCurrentFullIdentity()
145
+ {
146
+ var pool = Pool();
147
+ var owner = Consume(pool, 0);
148
+ var staleToken = Consume(Pool(), 0);
149
+ Assert.That(pool.PrepareOwnerTransfer(staleToken, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), Is.False);
150
+ Assert.That(pool.CompleteTrusted(staleToken), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.IdentityMismatchOrBlocked));
151
+
152
+ UnityCliAdmissionPool.ConsumedIdentity coordinator;
153
+ Assert.That(pool.PrepareOwnerTransfer(owner, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), Is.True);
154
+ Assert.That(pool.CommitOwnerTransfer(owner, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, out coordinator), Is.True);
155
+ Assert.That(pool.PrepareOwnerTransfer(owner, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup), Is.False);
156
+ Assert.That(pool.CompleteTrusted(owner), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.IdentityMismatchOrBlocked));
157
+ Assert.That(pool.Capture().Active, Is.EqualTo(1));
158
+ }
159
+
160
+ [Test]
161
+ public void FrozenTerminalIdentityCannotDeleteSameRequestIdSuccessor()
162
+ {
163
+ var pool = Pool();
164
+ var oldIdentity = Consume(pool, 0);
165
+ Assert.That(pool.CompleteTrusted(oldIdentity), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted));
166
+ var successor = Consume(pool, 0, 1);
167
+ Assert.That(pool.CompleteTrusted(oldIdentity), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.IdentityMismatchOrBlocked));
168
+ Assert.That(pool.Capture().Active, Is.EqualTo(1));
169
+ Assert.That(pool.CompleteTrusted(successor), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted));
170
+ }
171
+
172
+ [Test]
173
+ public void GrantTtlUsesMonotonicClockAndStaleGenerationRevokesWithoutConsumption()
88
174
  {
89
175
  long monotonic = 1000;
90
176
  long utc = 100000;
91
177
  var pool = new UnityCliAdmissionPool(InstanceId, SessionId, 5, 3, () => monotonic, () => utc);
92
- pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
178
+ var stale = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 7);
179
+ Assert.That(stale.Token, Has.Length.EqualTo(64));
180
+ Assert.That(pool.AuthorizeAndCapture(Request(0), Fingerprint(0), Digest(0), stale.Token, ListenerId, 8, 200000).Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.IdentityMismatch));
181
+ Assert.That(pool.Capture().Active, Is.EqualTo(0));
182
+
183
+ pool.Acquire(Request(1), Fingerprint(1), ListenerId, 8);
93
184
  utc = -100000;
94
185
  Assert.That(pool.Capture().Active, Is.EqualTo(1));
95
186
  monotonic = 3001;
@@ -97,76 +188,130 @@ namespace UnityCli.Tests
97
188
  }
98
189
 
99
190
  [Test]
100
- public void UnconsumedTokenExpiresOrRevokesAndReleasesSlot()
191
+ public void MaxPlusTwoGrantTimeoutsReturnToBaseline()
101
192
  {
102
193
  long now = 1000;
103
194
  var pool = Pool(() => now);
104
- var expired = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
195
+ var outcomes = Enumerable.Range(0, 7).Select(index => pool.Acquire(Request(index), Fingerprint(index), ListenerId, 1).Outcome).ToArray();
196
+ Assert.That(outcomes.Count(value => value == UnityCliAdmissionPool.AcquireOutcome.Granted), Is.EqualTo(5));
197
+ Assert.That(outcomes.Count(value => value == UnityCliAdmissionPool.AcquireOutcome.Closed), Is.EqualTo(2));
105
198
  now = 3001;
106
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), expired.Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Missing));
107
- Assert.That(pool.Capture().Active, Is.EqualTo(0));
108
- var revoked = pool.Acquire(Request(1), Fingerprint(1), ListenerId, 2);
109
- pool.RevokeUnconsumed(ListenerId, 2);
110
199
  Assert.That(pool.Capture().Active, Is.EqualTo(0));
111
- Assert.That(pool.Authorize(Request(1), Fingerprint(1), revoked.Token, ListenerId, 2, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Missing));
200
+ Assert.That(pool.Capture().GateClosed, Is.False);
112
201
  }
113
202
 
114
203
  [Test]
115
- public void StateTransitionsUseSeparateTerminalAndRevokePaths()
204
+ public void MaxPlusTwoPreQueueTimeoutsReturnToBaselineOrOneDurableOwner()
116
205
  {
117
206
  var pool = Pool();
118
- var grant = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
119
- Assert.That(pool.CompleteTrusted(Request(0), Fingerprint(0), grant.Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.False);
120
- Assert.That(pool.Revoke(Request(0), Fingerprint(0), grant.Token), Is.True);
121
- Assert.That(pool.Capture().Active, Is.EqualTo(0));
207
+ var baseline = pool.Capture().Active;
208
+ for (var index = 0; index < 7; index++)
209
+ {
210
+ var grant = pool.Acquire(Request(index), Fingerprint(index), ListenerId, 1);
211
+ if (grant.Outcome != UnityCliAdmissionPool.AcquireOutcome.Granted) continue;
212
+ var authorization = pool.AuthorizeAndCapture(Request(index), Fingerprint(index), Digest(index), grant.Token, ListenerId, 1, 5000);
213
+ Assert.That(authorization.Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
214
+ Assert.That(pool.CompensateAuthorization(authorization.Identity), Is.True);
215
+ Assert.That(pool.Capture().Active, Is.EqualTo(baseline));
216
+ }
122
217
 
123
- grant = pool.Acquire(Request(1), Fingerprint(1), ListenerId, 1);
124
- Assert.That(pool.Authorize(Request(1), Fingerprint(1), grant.Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
125
- Assert.That(pool.Revoke(Request(1), Fingerprint(1), grant.Token), Is.False);
126
- Assert.That(pool.CompleteTrusted(Request(1), Fingerprint(1), grant.Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
127
- Assert.That(pool.CompleteTrusted(Request(1), Fingerprint(1), grant.Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
128
- Assert.That(pool.Capture().Active, Is.EqualTo(0));
218
+ var durable = Consume(pool, 20);
219
+ UnityCliAdmissionPool.ConsumedIdentity coordinator;
220
+ Assert.That(pool.PrepareOwnerTransfer(durable, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), Is.True);
221
+ Assert.That(pool.CommitOwnerTransfer(durable, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, out coordinator), Is.True);
222
+ for (var index = 21; index < 28; index++)
223
+ {
224
+ var grant = pool.Acquire(Request(index), Fingerprint(index), ListenerId, 1);
225
+ if (grant.Outcome != UnityCliAdmissionPool.AcquireOutcome.Granted) continue;
226
+ var authorization = pool.AuthorizeAndCapture(Request(index), Fingerprint(index), Digest(index), grant.Token, ListenerId, 1, 5000);
227
+ Assert.That(pool.CompensateAuthorization(authorization.Identity), Is.True);
228
+ }
229
+ Assert.That(pool.Capture().Active, Is.EqualTo(1));
230
+ Assert.That(pool.Capture().Records.Single().OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.Coordinator));
129
231
  }
130
232
 
131
233
  [Test]
132
- public void OwnerTransferMakesPreviousWorkItemIdentityUnableToComplete()
234
+ public void TransferIntentSnapshotsConvergeBeforeAndAfterPoolTransfer()
235
+ {
236
+ var source = Pool();
237
+ var workItem = Consume(source, 0);
238
+ var beforeIntent = source.CaptureRecovery();
239
+ Assert.That(beforeIntent.Records.Single().TransferCommitted, Is.True);
240
+
241
+ Assert.That(source.PrepareOwnerTransfer(workItem, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), Is.True);
242
+ var afterIntent = source.CaptureRecovery();
243
+ Assert.That(afterIntent.Records.Single().OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem));
244
+ Assert.That(afterIntent.Records.Single().IntendedOwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.Coordinator));
245
+ Assert.That(afterIntent.Records.Single().TransferCommitted, Is.False);
246
+
247
+ UnityCliAdmissionPool.ConsumedIdentity committed;
248
+ Assert.That(source.CommitOwnerTransfer(workItem, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, out committed), Is.True);
249
+ Assert.That(source.CaptureRecovery().Records.Single().TransferCommitted, Is.True);
250
+
251
+ var beforeIntentRestore = Pool();
252
+ beforeIntentRestore.Restore(beforeIntent.Records, beforeIntent.Active, beforeIntent.GateClosed, beforeIntent.RecoveryDigest, beforeIntent.ConfiguredMaxActive, beforeIntent.ReopenAt);
253
+ UnityCliAdmissionPool.ConsumedIdentity unchanged;
254
+ Assert.That(beforeIntentRestore.TryGetConsumedIdentity(Request(0), out unchanged), Is.True);
255
+ Assert.That(unchanged.OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem));
256
+
257
+ // intent落盘后与pool已转移但commit marker未落盘都恢复同一pre-transfer snapshot。
258
+ foreach (var faultPoint in new[] { "after-intent-save", "after-pool-transfer-before-commit-save" })
259
+ {
260
+ var restored = Pool();
261
+ restored.Restore(afterIntent.Records, afterIntent.Active, afterIntent.GateClosed, afterIntent.RecoveryDigest, afterIntent.ConfiguredMaxActive, afterIntent.ReopenAt);
262
+ UnityCliAdmissionPool.ConsumedIdentity recoveredWorkItem;
263
+ Assert.That(restored.TryGetConsumedIdentity(Request(0), out recoveredWorkItem), Is.True, faultPoint);
264
+ Assert.That(restored.PrepareOwnerTransfer(recoveredWorkItem, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), Is.True, faultPoint);
265
+ UnityCliAdmissionPool.ConsumedIdentity recoveredCoordinator;
266
+ Assert.That(restored.CommitOwnerTransfer(recoveredWorkItem, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, out recoveredCoordinator), Is.True, faultPoint);
267
+ Assert.That(recoveredCoordinator.OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), faultPoint);
268
+ }
269
+ }
270
+
271
+ [TestCase(UnityCliAdmissionPool.AdmissionOwnerState.Coordinator)]
272
+ [TestCase(UnityCliAdmissionPool.AdmissionOwnerState.Continuation)]
273
+ [TestCase(UnityCliAdmissionPool.AdmissionOwnerState.Recovery)]
274
+ [TestCase(UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup)]
275
+ public void PendingTransferCannotCompleteOrDeleteTheWorkItemOwner(UnityCliAdmissionPool.AdmissionOwnerState intended)
133
276
  {
134
277
  var pool = Pool();
135
- var grant = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
136
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), grant.Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
137
- UnityCliAdmissionPool.ConsumedIdentity workItem;
138
- Assert.That(pool.TryGetConsumedIdentity(Request(0), out workItem), Is.True);
139
- Assert.That(workItem.OwnerKind, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem));
140
- Assert.That(pool.TransferOwner(workItem.RequestId, workItem.RootFingerprint, workItem.Token, workItem.SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.Coordinator), Is.True);
141
- Assert.That(pool.CompleteTrusted(workItem.RequestId, workItem.RootFingerprint, workItem.Token, workItem.SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.False);
142
- Assert.That(pool.CompleteTrusted(workItem.RequestId, workItem.RootFingerprint, workItem.Token, workItem.SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.Coordinator), Is.True);
278
+ var owner = Consume(pool, 0);
279
+ Assert.That(pool.PrepareOwnerTransfer(owner, intended), Is.True);
280
+ Assert.That(pool.CompleteTrusted(owner), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.IdentityMismatchOrBlocked));
281
+ var pending = pool.Capture().Records.Single();
282
+ Assert.That(pending.OwnerState, Is.EqualTo(UnityCliAdmissionPool.AdmissionOwnerState.WorkItem));
283
+ Assert.That(pending.IntendedOwnerState, Is.EqualTo(intended));
284
+ Assert.That(pending.TransferCommitted, Is.False);
285
+ UnityCliAdmissionPool.ConsumedIdentity successor;
286
+ Assert.That(pool.CommitOwnerTransfer(owner, intended, out successor), Is.True);
287
+ Assert.That(successor.OwnerState, Is.EqualTo(intended));
288
+ var expected = intended == UnityCliAdmissionPool.AdmissionOwnerState.Continuation
289
+ || intended == UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup
290
+ ? UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted
291
+ : UnityCliAdmissionPool.CompletionOutcome.IdentityMismatchOrBlocked;
292
+ Assert.That(pool.CompleteTrusted(successor), Is.EqualTo(expected));
143
293
  }
144
294
 
145
295
  [Test]
146
- public void FrozenTerminalIdentityCannotDeleteSameRequestIdSuccessor()
296
+ public void RecoverySnapshotExcludesGrantsAndRemainsBounded()
147
297
  {
148
298
  var pool = Pool();
149
- var oldGrant = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
150
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), oldGrant.Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
151
- UnityCliAdmissionPool.ConsumedIdentity oldIdentity;
152
- Assert.That(pool.TryGetConsumedIdentity(Request(0), out oldIdentity), Is.True);
153
- Assert.That(pool.CompleteTrusted(oldIdentity.RequestId, oldIdentity.RootFingerprint, oldIdentity.Token, oldIdentity.SessionId, oldIdentity.OwnerKind), Is.True);
154
-
155
- var successor = pool.Acquire(Request(0), Fingerprint(1), ListenerId, 1);
156
- Assert.That(pool.Authorize(Request(0), Fingerprint(1), successor.Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
157
- Assert.That(pool.CompleteTrusted(oldIdentity.RequestId, oldIdentity.RootFingerprint, oldIdentity.Token, oldIdentity.SessionId, oldIdentity.OwnerKind), Is.False);
158
- Assert.That(pool.Capture().Active, Is.EqualTo(1));
159
- Assert.That(pool.CompleteTrusted(Request(0), Fingerprint(1), successor.Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
299
+ Consume(pool, 0);
300
+ pool.Acquire(Request(1), Fingerprint(1), ListenerId, 1);
301
+ pool.Acquire(Request(2), Fingerprint(2), ListenerId, 1);
302
+ var recovery = pool.CaptureRecovery();
303
+ Assert.That(recovery.Active, Is.EqualTo(1));
304
+ Assert.That(recovery.Records.All(record => record.Consumed), Is.True);
305
+ Assert.That(recovery.Active, Is.LessThanOrEqualTo(recovery.ConfiguredMaxActive));
160
306
  }
161
307
 
162
308
  [Test]
163
- public void ValidRestorePreservesClosedHysteresisAndExactAttach()
309
+ public void RestorePreservesHysteresisExactAttachAndPendingTransfer()
164
310
  {
165
311
  var source = Pool();
166
- var grants = Enumerable.Range(0, 5).Select(index => source.Acquire(Request(index), Fingerprint(index), ListenerId, 1)).ToArray();
167
- for (var index = 0; index < grants.Length; index++) Assert.That(source.Authorize(Request(index), Fingerprint(index), grants[index].Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
168
- Assert.That(source.CompleteTrusted(Request(0), Fingerprint(0), grants[0].Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
169
- var before = source.Capture();
312
+ var owners = Enumerable.Range(0, 5).Select(index => Consume(source, index)).ToArray();
313
+ Assert.That(source.CompleteTrusted(owners[0]), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted));
314
+ var before = source.CaptureRecovery();
170
315
  Assert.That(before.Active, Is.EqualTo(4));
171
316
  Assert.That(before.GateClosed, Is.True);
172
317
 
@@ -174,67 +319,91 @@ namespace UnityCli.Tests
174
319
  restored.Restore(before.Records, before.Active, before.GateClosed, before.RecoveryDigest, before.ConfiguredMaxActive, before.ReopenAt);
175
320
  Assert.That(restored.Capture().Active, Is.EqualTo(4));
176
321
  Assert.That(restored.Capture().GateClosed, Is.True);
177
- var reacquire = restored.Acquire(Request(1), Fingerprint(1), "listener-after-reload", 2);
178
- Assert.That(reacquire.Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.AlreadyConsumed));
179
- Assert.That(reacquire.Token, Is.Null);
180
- Assert.That(restored.RecoverConsumed(Request(1), Fingerprint(1), "listener-after-reload", 2, 5000), Is.True);
181
- Assert.That(restored.AttachConsumed(Request(1), Fingerprint(1), "listener-after-reload", 2, 5000), Is.True);
182
- Assert.That(restored.Capture().Active, Is.EqualTo(4));
322
+ Assert.That(restored.RecoverConsumed(Request(1), Fingerprint(1), Digest(1), "listener-after-reload", 2, 5000), Is.True);
323
+ Assert.That(restored.AttachConsumed(Request(1), Fingerprint(1), Digest(1), "listener-after-reload", 2, 5000), Is.True);
183
324
  Assert.That(restored.Acquire(Request(6), Fingerprint(6), "listener-after-reload", 2).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.Closed));
184
325
  }
185
326
 
186
327
  [Test]
187
- public void SmallerConfigurationRecoversExistingRootsAndStaysClosedUntilNewReopenAt()
328
+ public void RestoreReopensExactlyAtThreeAfterFiveOwnerHysteresis()
188
329
  {
189
- var source = new UnityCliAdmissionPool(InstanceId, SessionId, 7, 5, () => 1000, () => 1000);
190
- var grants = Enumerable.Range(0, 6).Select(index => source.Acquire(Request(index), Fingerprint(index), ListenerId, 1)).ToArray();
191
- for (var index = 0; index < grants.Length; index++) Assert.That(source.Authorize(Request(index), Fingerprint(index), grants[index].Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
192
- var before = source.Capture();
193
-
330
+ var source = Pool();
331
+ var owners = Enumerable.Range(0, 5).Select(index => Consume(source, index)).ToArray();
332
+ var snapshot = source.CaptureRecovery();
333
+ Assert.That(snapshot.Active, Is.EqualTo(5));
334
+ Assert.That(snapshot.GateClosed, Is.True);
194
335
  var restored = Pool();
195
- restored.Restore(before.Records, before.Active, before.GateClosed, before.RecoveryDigest, before.ConfiguredMaxActive, before.ReopenAt);
196
- Assert.That(restored.Capture().Active, Is.EqualTo(6));
197
- Assert.That(restored.Capture().MaxActive, Is.EqualTo(6));
198
- Assert.That(restored.Capture().ConfiguredMaxActive, Is.EqualTo(5));
199
- Assert.That(restored.Capture().GateClosed, Is.True);
200
- Assert.That(restored.CompleteTrusted(Request(0), Fingerprint(0), grants[0].Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
201
- Assert.That(restored.CompleteTrusted(Request(1), Fingerprint(1), grants[1].Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
336
+ restored.Restore(snapshot.Records, snapshot.Active, snapshot.GateClosed, snapshot.RecoveryDigest, snapshot.ConfiguredMaxActive, snapshot.ReopenAt);
337
+ Assert.That(restored.CompleteTrusted(owners[0]), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted));
202
338
  Assert.That(restored.Capture().Active, Is.EqualTo(4));
203
339
  Assert.That(restored.Capture().GateClosed, Is.True);
204
- Assert.That(restored.CompleteTrusted(Request(2), Fingerprint(2), grants[2].Token, SessionId, UnityCliAdmissionPool.AdmissionOwnerKind.WorkItem), Is.True);
340
+ Assert.That(restored.CompleteTrusted(owners[1]), Is.EqualTo(UnityCliAdmissionPool.CompletionOutcome.ExactCurrentCompleted));
205
341
  Assert.That(restored.Capture().Active, Is.EqualTo(3));
206
342
  Assert.That(restored.Capture().GateClosed, Is.False);
207
- Assert.That(restored.Capture().MaxActive, Is.EqualTo(5));
343
+ Assert.That(restored.Acquire(Request(5), Fingerprint(5), ListenerId, 1).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.Granted));
344
+ }
345
+
346
+ [Test]
347
+ public void NewSessionRejectsRestoredMechanismState()
348
+ {
349
+ var source = Pool();
350
+ Consume(source, 0);
351
+ var snapshot = source.CaptureRecovery();
352
+ var restarted = new UnityCliAdmissionPool(InstanceId, "session-new", 5, 3, () => 1000, () => 1000);
353
+ Assert.Throws<InvalidOperationException>(() => restarted.Restore(snapshot.Records, snapshot.Active, snapshot.GateClosed, snapshot.RecoveryDigest, snapshot.ConfiguredMaxActive, snapshot.ReopenAt));
354
+ Assert.That(restarted.Capture().RecoveryBlocked, Is.True);
208
355
  }
209
356
 
210
357
  [Test]
211
358
  public void CorruptRestoreKeepsPriorStateAndBlocksAdmission()
212
359
  {
213
360
  var pool = Pool();
214
- var grant = pool.Acquire(Request(0), Fingerprint(0), ListenerId, 1);
215
- Assert.That(pool.Authorize(Request(0), Fingerprint(0), grant.Token, ListenerId, 1, 5000), Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
216
- var before = pool.Capture();
361
+ Consume(pool, 0);
362
+ var before = pool.CaptureRecovery();
217
363
  var corrupt = before.Records.Select(record => new UnityCliAdmissionPool.AdmissionRecord
218
364
  {
219
365
  InstanceId = "another-instance", SessionId = record.SessionId, RequestId = record.RequestId, RootFingerprint = record.RootFingerprint,
220
- Token = record.Token, ListenerId = record.ListenerId, ListenerGeneration = record.ListenerGeneration,
221
- GrantExpiresAtMs = record.GrantExpiresAtMs, ExecutionDeadlineMs = record.ExecutionDeadlineMs, State = record.State
366
+ RequestDigest = record.RequestDigest, Token = record.Token, ListenerId = record.ListenerId, ListenerGeneration = record.ListenerGeneration,
367
+ GrantExpiresAtMs = record.GrantExpiresAtMs, ExecutionDeadlineMs = record.ExecutionDeadlineMs,
368
+ OwnerState = record.OwnerState, IntendedOwnerState = record.IntendedOwnerState, TransferCommitted = record.TransferCommitted
222
369
  }).ToArray();
223
-
224
370
  Assert.Throws<InvalidOperationException>(() => pool.Restore(corrupt, before.Active, true, before.RecoveryDigest, before.ConfiguredMaxActive, before.ReopenAt));
225
- var blocked = pool.Capture();
226
- Assert.That(blocked.RecoveryBlocked, Is.True);
227
- Assert.That(blocked.GateClosed, Is.True);
228
- Assert.That(blocked.Active, Is.EqualTo(before.Active));
371
+ Assert.That(pool.Capture().RecoveryBlocked, Is.True);
229
372
  Assert.That(pool.Acquire(Request(1), Fingerprint(1), ListenerId, 1).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.RecoveryBlocked));
373
+ }
230
374
 
231
- var countMismatch = Pool();
232
- Assert.Throws<InvalidOperationException>(() => countMismatch.Restore(before.Records, before.Active + 1, false, before.RecoveryDigest, before.ConfiguredMaxActive, before.ReopenAt));
233
- Assert.That(countMismatch.Capture().RecoveryBlocked, Is.True);
375
+ [Test]
376
+ public void ExactRootIdentityIncludesListenerRebindingAndDeadline()
377
+ {
378
+ var pool = Pool();
379
+ var owner = Consume(pool, 0);
380
+ var wrongDeadline = UnityCliAdmissionPool.CreateConsumedIdentity(owner.RequestId, owner.RootFingerprint, owner.RequestDigest, owner.Token, owner.SessionId, owner.OwnerState, owner.ListenerId, owner.ListenerGeneration, owner.ExecutionDeadlineMs + 1);
234
381
 
235
- var digestMismatch = Pool();
236
- Assert.Throws<InvalidOperationException>(() => digestMismatch.Restore(before.Records, before.Active, before.GateClosed, DifferentDigest(before.RecoveryDigest), before.ConfiguredMaxActive, before.ReopenAt));
237
- Assert.That(digestMismatch.Acquire(Request(2), Fingerprint(2), ListenerId, 1).Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.RecoveryBlocked));
382
+ Assert.That(pool.RecoverConsumed(owner.RequestId, owner.RootFingerprint, owner.RequestDigest, "listener-after-reload", 2, owner.ExecutionDeadlineMs), Is.True);
383
+ Assert.That(pool.InspectExactIdentityFields(owner), Is.EqualTo(UnityCliAdmissionPool.ExactIdentityOutcome.IdentityMismatchOrBlocked));
384
+ Assert.That(pool.InspectExactIdentityFields(wrongDeadline), Is.EqualTo(UnityCliAdmissionPool.ExactIdentityOutcome.IdentityMismatchOrBlocked));
385
+ UnityCliAdmissionPool.ConsumedIdentity rebound;
386
+ Assert.That(pool.TryGetConsumedIdentity(owner.RequestId, out rebound), Is.True);
387
+ Assert.That(pool.InspectExactIdentityFields(rebound), Is.EqualTo(UnityCliAdmissionPool.ExactIdentityOutcome.ExactCurrent));
388
+ Assert.That(rebound.ListenerId, Is.EqualTo("listener-after-reload"));
389
+ Assert.That(rebound.ListenerGeneration, Is.EqualTo(2));
390
+ Assert.That(pool.Capture().Active, Is.EqualTo(1));
391
+ }
392
+
393
+ [Test]
394
+ public void RestoreConsumedRequiresTheExactTransferSnapshot()
395
+ {
396
+ var source = Pool();
397
+ var owner = Consume(source, 0);
398
+ Assert.That(source.PrepareOwnerTransfer(owner, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator), Is.True);
399
+ var pending = source.Capture().Records.Single();
400
+ var restored = Pool();
401
+
402
+ Assert.That(restored.RestoreConsumed(owner, UnityCliAdmissionPool.AdmissionOwnerState.Coordinator, false), Is.True);
403
+ Assert.That(restored.RestoreConsumed(owner, UnityCliAdmissionPool.AdmissionOwnerState.Recovery, false), Is.False);
404
+ Assert.That(restored.RestoreConsumed(owner, UnityCliAdmissionPool.AdmissionOwnerState.WorkItem, true), Is.False);
405
+ Assert.That(pending.TransferCommitted, Is.False);
406
+ Assert.That(restored.Capture().Active, Is.EqualTo(1));
238
407
  }
239
408
 
240
409
  [Test]
@@ -250,13 +419,41 @@ namespace UnityCli.Tests
250
419
  Assert.That(outcomes.Count(value => value == UnityCliAdmissionPool.AcquireOutcome.Granted), Is.EqualTo(5));
251
420
  }
252
421
 
422
+ private static UnityCliAdmissionPool.ConsumedIdentity Consume(UnityCliAdmissionPool pool, int index, int fingerprintIndex = -1)
423
+ {
424
+ var fingerprint = Fingerprint(fingerprintIndex < 0 ? index : fingerprintIndex);
425
+ var grant = pool.Acquire(Request(index), fingerprint, ListenerId, 1);
426
+ Assert.That(grant.Outcome, Is.EqualTo(UnityCliAdmissionPool.AcquireOutcome.Granted));
427
+ var authorization = pool.AuthorizeAndCapture(Request(index), fingerprint, Digest(index), grant.Token, ListenerId, 1, 5000);
428
+ Assert.That(authorization.Outcome, Is.EqualTo(UnityCliAdmissionPool.AuthorizeOutcome.Authorized));
429
+ return authorization.Identity;
430
+ }
431
+
432
+ private static UnityCliAdmissionPool.ConsumedIdentity OwnerAt(UnityCliAdmissionPool pool, UnityCliAdmissionPool.AdmissionOwnerState state)
433
+ {
434
+ var owner = Consume(pool, 0);
435
+ if (state == UnityCliAdmissionPool.AdmissionOwnerState.WorkItem) return owner;
436
+ if (state == UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup)
437
+ return Transfer(pool, owner, UnityCliAdmissionPool.AdmissionOwnerState.TerminalCleanup);
438
+ return Transfer(pool, owner, state);
439
+ }
440
+
441
+ private static UnityCliAdmissionPool.ConsumedIdentity Transfer(UnityCliAdmissionPool pool, UnityCliAdmissionPool.ConsumedIdentity owner, UnityCliAdmissionPool.AdmissionOwnerState target)
442
+ {
443
+ Assert.That(pool.PrepareOwnerTransfer(owner, target), Is.True);
444
+ UnityCliAdmissionPool.ConsumedIdentity successor;
445
+ Assert.That(pool.CommitOwnerTransfer(owner, target, out successor), Is.True);
446
+ return successor;
447
+ }
448
+
253
449
  private static UnityCliAdmissionPool Pool(Func<long> now = null)
254
450
  {
255
451
  var clock = now ?? delegate { return 1000L; };
256
452
  return new UnityCliAdmissionPool(InstanceId, SessionId, 5, 3, clock, clock);
257
453
  }
454
+ private static string Edge(UnityCliAdmissionPool.AdmissionOwnerState source, UnityCliAdmissionPool.AdmissionOwnerState target) { return source + "->" + target; }
258
455
  private static string Request(int index) { return "req-" + index.ToString(); }
259
- private static string DifferentDigest(string value) { return (value[0] == '0' ? "1" : "0") + value.Substring(1); }
260
456
  private static string Fingerprint(int index) { return index.ToString("x64"); }
457
+ private static string Digest(int index) { return (index + 1000).ToString("x64"); }
261
458
  }
262
459
  }