@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
@@ -0,0 +1,5 @@
1
+ #if UNITY_EDITOR
2
+ using System.Runtime.CompilerServices;
3
+
4
+ [assembly: InternalsVisibleTo("UnityCli.Editor.Tests")]
5
+ #endif
@@ -2,7 +2,7 @@
2
2
  "name": "UnityCli.Runtime",
3
3
  "rootNamespace": "UnityCli",
4
4
  "references": ["UnityCli.Shared"],
5
- "includePlatforms": ["WindowsStandalone32", "WindowsStandalone64"],
5
+ "includePlatforms": ["Editor", "WindowsStandalone32", "WindowsStandalone64"],
6
6
  "excludePlatforms": [],
7
7
  "allowUnsafeCode": false,
8
8
  "overrideReferences": false,
@@ -0,0 +1,4 @@
1
+ using System.Runtime.CompilerServices;
2
+
3
+ [assembly: InternalsVisibleTo("UnityCli.Editor")]
4
+ [assembly: InternalsVisibleTo("UnityCli.Editor.Tests")]
@@ -7,13 +7,14 @@ using System.Text;
7
7
 
8
8
  namespace UnityCli
9
9
  {
10
- /// <summary>每个Unity target session独占的有界原子准入池;terminal proof由既有业务owner持有。</summary>
10
+ /// <summary>每个Unity target session独占的有界原子准入池;owner state是唯一生命周期真相。</summary>
11
11
  public sealed class UnityCliAdmissionPool
12
12
  {
13
- public enum AdmissionState { Granted, Consumed }
14
- public enum AdmissionOwnerKind { WorkItem, Coordinator, Recovery }
13
+ public enum AdmissionOwnerState { Granted, WorkItem, Coordinator, Continuation, Recovery, TerminalCleanup }
15
14
  public enum AcquireOutcome { Granted, AlreadyConsumed, Closed, Invalid, IdentityMismatch, RecoveryBlocked }
16
- public enum AuthorizeOutcome { Authorized, AlreadyConsumed, Missing, Expired, IdentityMismatch, Invalid, RecoveryBlocked }
15
+ public enum AuthorizeOutcome { Authorized, AlreadyConsumed, Missing, IdentityMismatch, Invalid, RecoveryBlocked }
16
+ public enum ExactIdentityOutcome { ExactCurrent, DefinitelyAbsent, IdentityMismatchOrBlocked }
17
+ public enum CompletionOutcome { ExactCurrentCompleted, DefinitelyAbsent, IdentityMismatchOrBlocked }
17
18
 
18
19
  public sealed class AcquireResult
19
20
  {
@@ -24,29 +25,52 @@ namespace UnityCli
24
25
  public bool NewlyGranted { get; internal set; }
25
26
  }
26
27
 
28
+ public sealed class AuthorizeResult
29
+ {
30
+ public AuthorizeOutcome Outcome { get; internal set; }
31
+ public ConsumedIdentity Identity { get; internal set; }
32
+ public bool NewlyConsumed { get; internal set; }
33
+ }
34
+
27
35
  public sealed class AdmissionRecord
28
36
  {
29
37
  public string InstanceId { get; set; }
30
38
  public string SessionId { get; set; }
31
39
  public string RequestId { get; set; }
32
40
  public string RootFingerprint { get; set; }
41
+ public string RequestDigest { get; set; }
33
42
  public string Token { get; set; }
34
43
  public string ListenerId { get; set; }
35
44
  public long ListenerGeneration { get; set; }
36
45
  public long GrantExpiresAtMs { get; set; }
37
46
  public long ExecutionDeadlineMs { get; set; }
38
- public AdmissionState State { get; set; }
39
- public AdmissionOwnerKind OwnerKind { get; set; }
40
- public bool Consumed { get { return State == AdmissionState.Consumed; } }
47
+ public AdmissionOwnerState OwnerState { get; set; }
48
+ public AdmissionOwnerState IntendedOwnerState { get; set; }
49
+ public bool TransferCommitted { get; set; }
50
+ public bool Consumed { get { return OwnerState != AdmissionOwnerState.Granted; } }
41
51
  }
42
52
 
43
53
  public sealed class ConsumedIdentity
44
54
  {
45
55
  public string RequestId { get; internal set; }
46
56
  public string RootFingerprint { get; internal set; }
57
+ public string RequestDigest { get; internal set; }
47
58
  public string Token { get; internal set; }
48
59
  public string SessionId { get; internal set; }
49
- public AdmissionOwnerKind OwnerKind { get; internal set; }
60
+ public string ListenerId { get; internal set; }
61
+ public long ListenerGeneration { get; internal set; }
62
+ public long ExecutionDeadlineMs { get; internal set; }
63
+ public AdmissionOwnerState OwnerState { get; internal set; }
64
+ }
65
+
66
+ /// <summary>跨assembly owner只通过完整字段创建不可变transition identity。</summary>
67
+ public static ConsumedIdentity CreateConsumedIdentity(string requestId, string rootFingerprint, string requestDigest, string token, string ownerSessionId, AdmissionOwnerState ownerState, string listenerId, long listenerGeneration, long executionDeadlineMs)
68
+ {
69
+ if (!ValidRequestId(requestId) || !IsSha256(rootFingerprint) || !IsSha256(requestDigest) || !IsToken(token)
70
+ || string.IsNullOrWhiteSpace(ownerSessionId) || !ValidListener(listenerId, listenerGeneration) || executionDeadlineMs <= 0
71
+ || ownerState == AdmissionOwnerState.Granted || !Enum.IsDefined(typeof(AdmissionOwnerState), ownerState))
72
+ throw new ArgumentException("Admission consumed identity is invalid.");
73
+ return new ConsumedIdentity { RequestId = requestId, RootFingerprint = rootFingerprint, RequestDigest = requestDigest, Token = token, SessionId = ownerSessionId, ListenerId = listenerId, ListenerGeneration = listenerGeneration, ExecutionDeadlineMs = executionDeadlineMs, OwnerState = ownerState };
50
74
  }
51
75
 
52
76
  public sealed class Snapshot
@@ -104,12 +128,12 @@ namespace UnityCli
104
128
  if (records.TryGetValue(requestId, out existing))
105
129
  {
106
130
  if (!string.Equals(existing.RootFingerprint, rootFingerprint, StringComparison.Ordinal)) return Result(AcquireOutcome.IdentityMismatch);
107
- if (existing.State == AdmissionState.Granted && (!string.Equals(existing.ListenerId, listenerId, StringComparison.Ordinal) || existing.ListenerGeneration != listenerGeneration))
131
+ if (existing.OwnerState == AdmissionOwnerState.Granted && (!string.Equals(existing.ListenerId, listenerId, StringComparison.Ordinal) || existing.ListenerGeneration != listenerGeneration))
108
132
  {
109
133
  records.Remove(requestId);
110
134
  UpdateGateAfterRemoval();
111
135
  }
112
- else if (existing.State == AdmissionState.Consumed) return Result(AcquireOutcome.AlreadyConsumed);
136
+ else if (existing.Consumed) return Result(AcquireOutcome.AlreadyConsumed);
113
137
  else return Result(AcquireOutcome.Granted, existing.Token, RemainingGrantMs(existing), false);
114
138
  }
115
139
 
@@ -136,8 +160,9 @@ namespace UnityCli
136
160
  ListenerGeneration = listenerGeneration,
137
161
  GrantExpiresAtMs = checked(monotonicNowMs() + GrantTtlMs),
138
162
  ExecutionDeadlineMs = 0,
139
- State = AdmissionState.Granted,
140
- OwnerKind = AdmissionOwnerKind.WorkItem
163
+ OwnerState = AdmissionOwnerState.Granted,
164
+ IntendedOwnerState = AdmissionOwnerState.Granted,
165
+ TransferCommitted = true
141
166
  });
142
167
  if (records.Count == maxActive) gateClosed = true;
143
168
  return Result(AcquireOutcome.Granted, token, GrantTtlMs, true);
@@ -151,38 +176,36 @@ namespace UnityCli
151
176
  }
152
177
  }
153
178
 
154
- /// <summary>首次业务POST只允许一次Granted→Consumed;generation mismatch原子撤销旧grant。</summary>
155
- public AuthorizeOutcome Authorize(string requestId, string rootFingerprint, string token, string listenerId, long listenerGeneration, long executionDeadlineMs)
179
+ /// <summary>Granted→WorkItem与完整identity冻结共享一个线性化点。</summary>
180
+ public AuthorizeResult AuthorizeAndCapture(string requestId, string rootFingerprint, string requestDigest, string token, string listenerId, long listenerGeneration, long executionDeadlineMs)
156
181
  {
157
182
  lock (sync)
158
183
  {
159
184
  try
160
185
  {
161
186
  CleanupExpiredGrants();
162
- if (recoveryBlocked) return AuthorizeOutcome.RecoveryBlocked;
163
- if (!ValidRequestId(requestId) || !IsSha256(rootFingerprint) || !IsToken(token) || !ValidListener(listenerId, listenerGeneration) || executionDeadlineMs <= utcNowMs()) return AuthorizeOutcome.Invalid;
187
+ if (recoveryBlocked) return Authorized(AuthorizeOutcome.RecoveryBlocked);
188
+ if (!ValidRequestId(requestId) || !IsSha256(rootFingerprint) || !IsSha256(requestDigest) || !IsToken(token) || !ValidListener(listenerId, listenerGeneration) || executionDeadlineMs <= utcNowMs()) return Authorized(AuthorizeOutcome.Invalid);
164
189
  AdmissionRecord entry;
165
- if (!records.TryGetValue(requestId, out entry)) return AuthorizeOutcome.Missing;
166
- if (!string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal) || !FixedEquals(entry.Token, token)) return AuthorizeOutcome.IdentityMismatch;
167
- if (entry.State == AdmissionState.Consumed)
168
- return entry.ExecutionDeadlineMs == executionDeadlineMs ? AuthorizeOutcome.AlreadyConsumed : AuthorizeOutcome.IdentityMismatch;
169
- if (entry.GrantExpiresAtMs <= monotonicNowMs())
170
- {
171
- records.Remove(requestId);
172
- UpdateGateAfterRemoval();
173
- return AuthorizeOutcome.Expired;
174
- }
190
+ if (!records.TryGetValue(requestId, out entry)) return Authorized(AuthorizeOutcome.Missing);
191
+ if (!string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal) || !FixedEquals(entry.Token, token)) return Authorized(AuthorizeOutcome.IdentityMismatch);
192
+ if (entry.Consumed)
193
+ return entry.ExecutionDeadlineMs == executionDeadlineMs && string.Equals(entry.RequestDigest, requestDigest, StringComparison.Ordinal)
194
+ ? Authorized(AuthorizeOutcome.AlreadyConsumed, Identity(entry))
195
+ : Authorized(AuthorizeOutcome.IdentityMismatch);
175
196
  if (!string.Equals(entry.ListenerId, listenerId, StringComparison.Ordinal) || entry.ListenerGeneration != listenerGeneration)
176
197
  {
177
198
  records.Remove(requestId);
178
199
  UpdateGateAfterRemoval();
179
- return AuthorizeOutcome.IdentityMismatch;
200
+ return Authorized(AuthorizeOutcome.IdentityMismatch);
180
201
  }
181
- entry.State = AdmissionState.Consumed;
182
- entry.OwnerKind = AdmissionOwnerKind.WorkItem;
202
+ entry.RequestDigest = requestDigest;
203
+ entry.OwnerState = AdmissionOwnerState.WorkItem;
204
+ entry.IntendedOwnerState = AdmissionOwnerState.WorkItem;
205
+ entry.TransferCommitted = true;
183
206
  entry.ExecutionDeadlineMs = executionDeadlineMs;
184
207
  entry.GrantExpiresAtMs = 0;
185
- return AuthorizeOutcome.Authorized;
208
+ return Authorized(AuthorizeOutcome.Authorized, Identity(entry), true);
186
209
  }
187
210
  catch
188
211
  {
@@ -193,29 +216,46 @@ namespace UnityCli
193
216
  }
194
217
  }
195
218
 
219
+ /// <summary>仅撤销本次新消费且尚未发布的WorkItem;完整identity避免删除后继。</summary>
220
+ public bool CompensateAuthorization(ConsumedIdentity identity)
221
+ {
222
+ lock (sync)
223
+ {
224
+ AdmissionRecord entry;
225
+ if (identity == null || !records.TryGetValue(identity.RequestId, out entry)) return true;
226
+ if (!Matches(entry, identity) || entry.OwnerState != AdmissionOwnerState.WorkItem
227
+ || entry.IntendedOwnerState != AdmissionOwnerState.WorkItem || !entry.TransferCommitted) return false;
228
+ records.Remove(identity.RequestId);
229
+ UpdateGateAfterRemoval();
230
+ return true;
231
+ }
232
+ }
233
+
196
234
  /// <summary>同listener generation的已消费root只做attach检查,不再次消费token。</summary>
197
- public bool AttachConsumed(string requestId, string rootFingerprint, string listenerId, long listenerGeneration, long executionDeadlineMs)
235
+ public bool AttachConsumed(string requestId, string rootFingerprint, string requestDigest, string listenerId, long listenerGeneration, long executionDeadlineMs)
198
236
  {
199
237
  lock (sync)
200
238
  {
201
239
  if (recoveryBlocked) return false;
202
240
  AdmissionRecord entry;
203
- return records.TryGetValue(requestId, out entry) && entry.State == AdmissionState.Consumed
241
+ return records.TryGetValue(requestId, out entry) && ValidConsumedRecord(entry)
204
242
  && string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal)
243
+ && string.Equals(entry.RequestDigest, requestDigest, StringComparison.Ordinal)
205
244
  && string.Equals(entry.ListenerId, listenerId, StringComparison.Ordinal) && entry.ListenerGeneration == listenerGeneration
206
245
  && entry.ExecutionDeadlineMs == executionDeadlineMs;
207
246
  }
208
247
  }
209
248
 
210
- /// <summary>durable owner证明同root恢复后显式重绑listener generation,不改变active。</summary>
211
- public bool RecoverConsumed(string requestId, string rootFingerprint, string listenerId, long listenerGeneration, long executionDeadlineMs)
249
+ /// <summary>durable owner证明同root恢复后显式重绑listener generation,不改变owner。</summary>
250
+ public bool RecoverConsumed(string requestId, string rootFingerprint, string requestDigest, string listenerId, long listenerGeneration, long executionDeadlineMs)
212
251
  {
213
252
  lock (sync)
214
253
  {
215
254
  if (recoveryBlocked || !ValidListener(listenerId, listenerGeneration)) return false;
216
255
  AdmissionRecord entry;
217
- if (!records.TryGetValue(requestId, out entry) || entry.State != AdmissionState.Consumed
256
+ if (!records.TryGetValue(requestId, out entry) || !ValidConsumedRecord(entry)
218
257
  || !string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal)
258
+ || !string.Equals(entry.RequestDigest, requestDigest, StringComparison.Ordinal)
219
259
  || entry.ExecutionDeadlineMs != executionDeadlineMs) return false;
220
260
  entry.ListenerId = listenerId;
221
261
  entry.ListenerGeneration = listenerGeneration;
@@ -223,7 +263,49 @@ namespace UnityCli
223
263
  }
224
264
  }
225
265
 
226
- /// <summary>terminal owner在写marker/绑定状态时冻结独立identity,禁止完成时自读当前owner。</summary>
266
+ /// <summary>marker与pool同时落在reload窗口时按完整identity重建唯一consumed record。</summary>
267
+ public bool RestoreConsumed(ConsumedIdentity identity, AdmissionOwnerState intendedOwner, bool transferCommitted)
268
+ {
269
+ lock (sync)
270
+ {
271
+ if (recoveryBlocked || identity == null || identity.OwnerState == AdmissionOwnerState.Granted
272
+ || !Enum.IsDefined(typeof(AdmissionOwnerState), intendedOwner) || intendedOwner == AdmissionOwnerState.Granted
273
+ || !ValidRequestId(identity.RequestId) || !IsSha256(identity.RootFingerprint) || !IsSha256(identity.RequestDigest)
274
+ || !IsToken(identity.Token) || !string.Equals(identity.SessionId, sessionId, StringComparison.Ordinal)
275
+ || !ValidListener(identity.ListenerId, identity.ListenerGeneration) || identity.ExecutionDeadlineMs <= 0
276
+ || !IsLegalOwnerTransfer(identity.OwnerState, intendedOwner)) return false;
277
+ AdmissionRecord existing;
278
+ if (records.TryGetValue(identity.RequestId, out existing))
279
+ {
280
+ var expectedIntendedOwner = transferCommitted ? identity.OwnerState : intendedOwner;
281
+ return Matches(existing, identity)
282
+ && existing.IntendedOwnerState == expectedIntendedOwner
283
+ && existing.TransferCommitted == transferCommitted;
284
+ }
285
+ if (records.Count >= maxActive) { gateClosed = true; return false; }
286
+ records.Add(identity.RequestId, new AdmissionRecord
287
+ {
288
+ InstanceId = instanceId,
289
+ SessionId = sessionId,
290
+ RequestId = identity.RequestId,
291
+ RootFingerprint = identity.RootFingerprint,
292
+ RequestDigest = identity.RequestDigest,
293
+ Token = identity.Token,
294
+ ListenerId = identity.ListenerId,
295
+ ListenerGeneration = identity.ListenerGeneration,
296
+ GrantExpiresAtMs = 0,
297
+ ExecutionDeadlineMs = identity.ExecutionDeadlineMs,
298
+ OwnerState = identity.OwnerState,
299
+ IntendedOwnerState = transferCommitted ? identity.OwnerState : intendedOwner,
300
+ TransferCommitted = transferCommitted
301
+ });
302
+ effectiveMaxActive = Math.Max(effectiveMaxActive, records.Count);
303
+ if (records.Count >= maxActive) gateClosed = true;
304
+ return true;
305
+ }
306
+ }
307
+
308
+ /// <summary>冻结当前consumed identity;后续转移与完成仍必须提交全部字段和expected owner。</summary>
227
309
  public bool TryGetConsumedIdentity(string requestId, out ConsumedIdentity identity)
228
310
  {
229
311
  lock (sync)
@@ -231,52 +313,114 @@ namespace UnityCli
231
313
  identity = null;
232
314
  AdmissionRecord entry;
233
315
  if (!records.TryGetValue(requestId, out entry) || !ValidConsumedRecord(entry)) return false;
234
- identity = new ConsumedIdentity { RequestId = entry.RequestId, RootFingerprint = entry.RootFingerprint, Token = entry.Token, SessionId = entry.SessionId, OwnerKind = entry.OwnerKind };
316
+ identity = Identity(entry);
235
317
  return true;
236
318
  }
237
319
  }
238
320
 
239
- /// <summary>只有当前WorkItem owner可按完整identity原子转移给coordinator或reload recovery。</summary>
240
- public bool TransferOwner(string requestId, string rootFingerprint, string token, string ownerSessionId, AdmissionOwnerKind targetOwner)
321
+ /// <summary>持久化层写完intent后登记expected→intended;重复同一intent幂等。</summary>
322
+ public bool PrepareOwnerTransfer(ConsumedIdentity identity, AdmissionOwnerState intendedOwner)
241
323
  {
242
324
  lock (sync)
243
325
  {
244
- if (targetOwner == AdmissionOwnerKind.WorkItem) return false;
245
326
  AdmissionRecord entry;
246
- if (!records.TryGetValue(requestId, out entry) || !ValidConsumedRecord(entry)
247
- || !string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal) || !FixedEquals(entry.Token, token)
248
- || !string.Equals(entry.SessionId, ownerSessionId, StringComparison.Ordinal)) return false;
249
- if (entry.OwnerKind == targetOwner) return true;
250
- if (entry.OwnerKind != AdmissionOwnerKind.WorkItem) return false;
251
- entry.OwnerKind = targetOwner;
327
+ if (identity == null || !records.TryGetValue(identity.RequestId, out entry) || !Matches(entry, identity)) return false;
328
+ if (entry.TransferCommitted && entry.OwnerState == intendedOwner && entry.IntendedOwnerState == intendedOwner) return true;
329
+ if (!entry.TransferCommitted) return entry.OwnerState == identity.OwnerState && entry.IntendedOwnerState == intendedOwner;
330
+ if (!LegalTransfer(entry.OwnerState, intendedOwner)) return false;
331
+ entry.IntendedOwnerState = intendedOwner;
332
+ entry.TransferCommitted = false;
252
333
  return true;
253
334
  }
254
335
  }
255
336
 
256
- /// <summary>Unity terminal owner必须提交冻结的完整identity与owner kind,旧owner不能删除同requestId后继。</summary>
257
- public bool CompleteTrusted(string requestId, string rootFingerprint, string token, string ownerSessionId, AdmissionOwnerKind ownerKind)
337
+ /// <summary>只提交已登记的exact transfer;已提交同一target时幂等返回冻结后继identity。</summary>
338
+ public bool CommitOwnerTransfer(ConsumedIdentity identity, AdmissionOwnerState intendedOwner, out ConsumedIdentity successor)
258
339
  {
259
340
  lock (sync)
260
341
  {
342
+ successor = null;
261
343
  AdmissionRecord entry;
262
- if (!records.TryGetValue(requestId, out entry)) return true;
263
- if (!ValidConsumedRecord(entry) || !string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal)
264
- || !FixedEquals(entry.Token, token) || !string.Equals(entry.SessionId, ownerSessionId, StringComparison.Ordinal)
265
- || entry.OwnerKind != ownerKind) return false;
266
- records.Remove(requestId);
267
- UpdateGateAfterRemoval();
344
+ if (identity == null || !records.TryGetValue(identity.RequestId, out entry)) return false;
345
+ if (entry.TransferCommitted && entry.OwnerState == intendedOwner && entry.IntendedOwnerState == intendedOwner
346
+ && MatchesIdentityFields(entry, identity) && (identity.OwnerState == intendedOwner || LegalTransfer(identity.OwnerState, intendedOwner)))
347
+ {
348
+ successor = Identity(entry);
349
+ return true;
350
+ }
351
+ if (!Matches(entry, identity) || entry.TransferCommitted || entry.IntendedOwnerState != intendedOwner
352
+ || !LegalTransfer(entry.OwnerState, intendedOwner)) return false;
353
+ entry.OwnerState = intendedOwner;
354
+ entry.IntendedOwnerState = intendedOwner;
355
+ entry.TransferCommitted = true;
356
+ successor = Identity(entry);
268
357
  return true;
269
358
  }
270
359
  }
271
360
 
272
- /// <summary>未消费grant只能按完整identity执行Granted→revoked→removed。</summary>
361
+ /// <summary>transfer marker按冻结字段识别当前root;owner state由同一锁内的current record另行裁决。</summary>
362
+ public ExactIdentityOutcome InspectExactIdentityFields(ConsumedIdentity identity)
363
+ {
364
+ lock (sync)
365
+ {
366
+ if (identity == null || recoveryBlocked) return ExactIdentityOutcome.IdentityMismatchOrBlocked;
367
+ AdmissionRecord entry;
368
+ if (!records.TryGetValue(identity.RequestId, out entry)) return ExactIdentityOutcome.DefinitelyAbsent;
369
+ return ValidConsumedRecord(entry) && MatchesIdentityFields(entry, identity)
370
+ ? ExactIdentityOutcome.ExactCurrent
371
+ : ExactIdentityOutcome.IdentityMismatchOrBlocked;
372
+ }
373
+ }
374
+
375
+ /// <summary>只有WorkItem、Continuation或TerminalCleanup可完成;返回值保留absent与mismatch差异。</summary>
376
+ public CompletionOutcome CompleteTrusted(ConsumedIdentity identity)
377
+ {
378
+ lock (sync)
379
+ {
380
+ if (identity == null || recoveryBlocked) return CompletionOutcome.IdentityMismatchOrBlocked;
381
+ AdmissionRecord entry;
382
+ if (!records.TryGetValue(identity.RequestId, out entry)) return CompletionOutcome.DefinitelyAbsent;
383
+ if (!ValidConsumedRecord(entry) || !Matches(entry, identity) || !CanComplete(entry.OwnerState)
384
+ || !entry.TransferCommitted || entry.IntendedOwnerState != entry.OwnerState)
385
+ return CompletionOutcome.IdentityMismatchOrBlocked;
386
+ records.Remove(identity.RequestId);
387
+ UpdateGateAfterRemoval();
388
+ return CompletionOutcome.ExactCurrentCompleted;
389
+ }
390
+ }
391
+
392
+ /// <summary>session publication关闭后,shutdown可按完整WorkItem身份隔离回收,即使普通recovery已阻断。</summary>
393
+ public CompletionOutcome CompleteWorkItemForShutdown(ConsumedIdentity identity)
394
+ {
395
+ lock (sync)
396
+ {
397
+ if (identity == null) return CompletionOutcome.IdentityMismatchOrBlocked;
398
+ AdmissionRecord entry;
399
+ if (!records.TryGetValue(identity.RequestId, out entry)) return CompletionOutcome.DefinitelyAbsent;
400
+ if (!ValidConsumedRecord(entry) || !Matches(entry, identity)
401
+ || entry.OwnerState != AdmissionOwnerState.WorkItem
402
+ || !entry.TransferCommitted || entry.IntendedOwnerState != AdmissionOwnerState.WorkItem)
403
+ return CompletionOutcome.IdentityMismatchOrBlocked;
404
+ records.Remove(identity.RequestId);
405
+ UpdateGateAfterRemoval();
406
+ return CompletionOutcome.ExactCurrentCompleted;
407
+ }
408
+ }
409
+
410
+ public static bool IsLegalOwnerTransfer(AdmissionOwnerState source, AdmissionOwnerState target)
411
+ {
412
+ return source == target || LegalTransfer(source, target);
413
+ }
414
+
415
+ /// <summary>未消费grant只能按完整identity执行Granted→removed。</summary>
273
416
  public bool Revoke(string requestId, string rootFingerprint, string token)
274
417
  {
275
418
  lock (sync)
276
419
  {
277
420
  AdmissionRecord entry;
278
421
  if (!records.TryGetValue(requestId, out entry)) return true;
279
- if (!RecordOwnedByPool(entry) || entry.State != AdmissionState.Granted || !string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal) || !FixedEquals(entry.Token, token)) return false;
422
+ if (!RecordOwnedByPool(entry) || entry.OwnerState != AdmissionOwnerState.Granted
423
+ || !string.Equals(entry.RootFingerprint, rootFingerprint, StringComparison.Ordinal) || !FixedEquals(entry.Token, token)) return false;
280
424
  records.Remove(requestId);
281
425
  UpdateGateAfterRemoval();
282
426
  return true;
@@ -287,7 +431,7 @@ namespace UnityCli
287
431
  {
288
432
  lock (sync)
289
433
  {
290
- var remove = records.Values.Where(entry => entry.State == AdmissionState.Granted && string.Equals(entry.ListenerId, listenerId, StringComparison.Ordinal) && entry.ListenerGeneration == listenerGeneration).Select(entry => entry.RequestId).ToArray();
434
+ var remove = records.Values.Where(entry => entry.OwnerState == AdmissionOwnerState.Granted && string.Equals(entry.ListenerId, listenerId, StringComparison.Ordinal) && entry.ListenerGeneration == listenerGeneration).Select(entry => entry.RequestId).ToArray();
291
435
  foreach (var requestId in remove) records.Remove(requestId);
292
436
  UpdateGateAfterRemoval();
293
437
  }
@@ -298,22 +442,22 @@ namespace UnityCli
298
442
  lock (sync)
299
443
  {
300
444
  CleanupExpiredGrants();
301
- var values = records.Values.OrderBy(value => value.RequestId, StringComparer.Ordinal).Select(Clone).ToArray();
302
- return new Snapshot
303
- {
304
- GateClosed = gateClosed,
305
- Active = values.Length,
306
- MaxActive = effectiveMaxActive,
307
- ConfiguredMaxActive = maxActive,
308
- ReopenAt = reopenAt,
309
- RecoveryBlocked = recoveryBlocked,
310
- Records = values,
311
- RecoveryDigest = ComputeRecoveryDigest(values, values.Length, gateClosed, maxActive, reopenAt)
312
- };
445
+ return SnapshotOf(records.Values.OrderBy(value => value.RequestId, StringComparer.Ordinal).Select(Clone).ToArray(), gateClosed, maxActive);
446
+ }
447
+ }
448
+
449
+ /// <summary>reload快照排除可撤销grant;digest与active count只覆盖durable consumed owner。</summary>
450
+ public Snapshot CaptureRecovery()
451
+ {
452
+ lock (sync)
453
+ {
454
+ CleanupExpiredGrants();
455
+ var values = records.Values.Where(value => value.Consumed).OrderBy(value => value.RequestId, StringComparer.Ordinal).Select(Clone).ToArray();
456
+ return SnapshotOf(values, values.Length >= maxActive || (gateClosed && values.Length > reopenAt), Math.Max(maxActive, values.Length));
313
457
  }
314
458
  }
315
459
 
316
- /// <summary>只在全部consumed active、声明count和digest验证成功后原子替换;不提供清空恢复fallback。</summary>
460
+ /// <summary>只恢复当前启动的consumed owner;未提交transfer由durable marker reconciliation裁决。</summary>
317
461
  public void Restore(IEnumerable<AdmissionRecord> values, int expectedActiveCount, bool wasClosed, string expectedRecoveryDigest, int sourceMaxActive, int sourceReopenAt)
318
462
  {
319
463
  lock (sync)
@@ -351,10 +495,25 @@ namespace UnityCli
351
495
  public void BlockRecovery() { lock (sync) { recoveryBlocked = true; gateClosed = true; } }
352
496
  public void Reset() { lock (sync) { records.Clear(); effectiveMaxActive = maxActive; gateClosed = false; recoveryBlocked = false; } }
353
497
 
498
+ private Snapshot SnapshotOf(AdmissionRecord[] values, bool closed, int digestMaxActive)
499
+ {
500
+ return new Snapshot
501
+ {
502
+ GateClosed = closed,
503
+ Active = values.Length,
504
+ MaxActive = effectiveMaxActive,
505
+ ConfiguredMaxActive = digestMaxActive,
506
+ ReopenAt = reopenAt,
507
+ RecoveryBlocked = recoveryBlocked,
508
+ Records = values,
509
+ RecoveryDigest = ComputeRecoveryDigest(values, values.Length, closed, digestMaxActive, reopenAt)
510
+ };
511
+ }
512
+
354
513
  private void CleanupExpiredGrants()
355
514
  {
356
515
  var current = monotonicNowMs();
357
- var remove = records.Values.Where(entry => entry.State == AdmissionState.Granted && entry.GrantExpiresAtMs <= current).Select(entry => entry.RequestId).ToArray();
516
+ var remove = records.Values.Where(entry => entry.OwnerState == AdmissionOwnerState.Granted && entry.GrantExpiresAtMs <= current).Select(entry => entry.RequestId).ToArray();
358
517
  foreach (var requestId in remove) records.Remove(requestId);
359
518
  if (remove.Length > 0) UpdateGateAfterRemoval();
360
519
  }
@@ -372,9 +531,10 @@ namespace UnityCli
372
531
  Append(builder, digestReopenAt.ToString(CultureInfo.InvariantCulture)); Append(builder, expectedActiveCount.ToString(CultureInfo.InvariantCulture)); Append(builder, wasClosed ? "1" : "0");
373
532
  foreach (var value in values.OrderBy(item => item.RequestId, StringComparer.Ordinal))
374
533
  {
375
- Append(builder, value.InstanceId); Append(builder, value.SessionId); Append(builder, value.RequestId); Append(builder, value.RootFingerprint); Append(builder, value.Token);
534
+ Append(builder, value.InstanceId); Append(builder, value.SessionId); Append(builder, value.RequestId); Append(builder, value.RootFingerprint); Append(builder, value.RequestDigest); Append(builder, value.Token);
376
535
  Append(builder, value.ListenerId); Append(builder, value.ListenerGeneration.ToString(CultureInfo.InvariantCulture)); Append(builder, value.GrantExpiresAtMs.ToString(CultureInfo.InvariantCulture));
377
- Append(builder, value.ExecutionDeadlineMs.ToString(CultureInfo.InvariantCulture)); Append(builder, ((int)value.State).ToString(CultureInfo.InvariantCulture)); Append(builder, ((int)value.OwnerKind).ToString(CultureInfo.InvariantCulture));
536
+ Append(builder, value.ExecutionDeadlineMs.ToString(CultureInfo.InvariantCulture)); Append(builder, ((int)value.OwnerState).ToString(CultureInfo.InvariantCulture));
537
+ Append(builder, ((int)value.IntendedOwnerState).ToString(CultureInfo.InvariantCulture)); Append(builder, value.TransferCommitted ? "1" : "0");
378
538
  }
379
539
  using (var sha = SHA256.Create())
380
540
  {
@@ -385,6 +545,21 @@ namespace UnityCli
385
545
  }
386
546
  }
387
547
 
548
+ private static bool LegalTransfer(AdmissionOwnerState source, AdmissionOwnerState target)
549
+ {
550
+ if (source == AdmissionOwnerState.WorkItem)
551
+ return target == AdmissionOwnerState.Coordinator || target == AdmissionOwnerState.Continuation
552
+ || target == AdmissionOwnerState.Recovery || target == AdmissionOwnerState.TerminalCleanup;
553
+ if (source == AdmissionOwnerState.Coordinator || source == AdmissionOwnerState.Recovery)
554
+ return target == AdmissionOwnerState.TerminalCleanup;
555
+ return false;
556
+ }
557
+
558
+ private static bool CanComplete(AdmissionOwnerState state)
559
+ {
560
+ return state == AdmissionOwnerState.WorkItem || state == AdmissionOwnerState.Continuation || state == AdmissionOwnerState.TerminalCleanup;
561
+ }
562
+
388
563
  private static void Append(StringBuilder builder, string value)
389
564
  {
390
565
  var item = value ?? string.Empty;
@@ -393,10 +568,25 @@ namespace UnityCli
393
568
 
394
569
  private int RemainingGrantMs(AdmissionRecord value) { return checked((int)Math.Max(0, Math.Min(GrantTtlMs, value.GrantExpiresAtMs - monotonicNowMs()))); }
395
570
  private static AcquireResult Result(AcquireOutcome outcome, string token = null, int grantExpiresInMs = 0, bool newlyGranted = false) { return new AcquireResult { Outcome = outcome, Token = token, RetryAfterMs = RetryAfterMs, GrantExpiresInMs = grantExpiresInMs, NewlyGranted = newlyGranted }; }
571
+ private static AuthorizeResult Authorized(AuthorizeOutcome outcome, ConsumedIdentity identity = null, bool newlyConsumed = false) { return new AuthorizeResult { Outcome = outcome, Identity = identity, NewlyConsumed = newlyConsumed }; }
396
572
  private bool RecordOwnedByPool(AdmissionRecord value) { return value != null && string.Equals(value.InstanceId, instanceId, StringComparison.Ordinal) && string.Equals(value.SessionId, sessionId, StringComparison.Ordinal) && ValidRequestId(value.RequestId) && IsSha256(value.RootFingerprint) && IsToken(value.Token) && ValidListener(value.ListenerId, value.ListenerGeneration); }
397
- private bool ValidConsumedRecord(AdmissionRecord value) { return RecordOwnedByPool(value) && value.State == AdmissionState.Consumed && Enum.IsDefined(typeof(AdmissionOwnerKind), value.OwnerKind) && value.GrantExpiresAtMs == 0 && value.ExecutionDeadlineMs > 0; }
398
- private AdmissionRecord Clone(AdmissionRecord value) { return new AdmissionRecord { InstanceId = value.InstanceId, SessionId = value.SessionId, RequestId = value.RequestId, RootFingerprint = value.RootFingerprint, Token = value.Token, ListenerId = value.ListenerId, ListenerGeneration = value.ListenerGeneration, GrantExpiresAtMs = value.GrantExpiresAtMs, ExecutionDeadlineMs = value.ExecutionDeadlineMs, State = value.State, OwnerKind = value.OwnerKind }; }
573
+ private bool ValidConsumedRecord(AdmissionRecord value) { return RecordOwnedByPool(value) && value.Consumed && IsSha256(value.RequestDigest) && Enum.IsDefined(typeof(AdmissionOwnerState), value.OwnerState) && Enum.IsDefined(typeof(AdmissionOwnerState), value.IntendedOwnerState) && value.GrantExpiresAtMs == 0 && value.ExecutionDeadlineMs > 0 && ((value.TransferCommitted && value.OwnerState == value.IntendedOwnerState) || (!value.TransferCommitted && LegalTransfer(value.OwnerState, value.IntendedOwnerState))); }
574
+ private AdmissionRecord Clone(AdmissionRecord value) { return new AdmissionRecord { InstanceId = value.InstanceId, SessionId = value.SessionId, RequestId = value.RequestId, RootFingerprint = value.RootFingerprint, RequestDigest = value.RequestDigest, Token = value.Token, ListenerId = value.ListenerId, ListenerGeneration = value.ListenerGeneration, GrantExpiresAtMs = value.GrantExpiresAtMs, ExecutionDeadlineMs = value.ExecutionDeadlineMs, OwnerState = value.OwnerState, IntendedOwnerState = value.IntendedOwnerState, TransferCommitted = value.TransferCommitted }; }
399
575
  private bool ValidRestoredRecord(AdmissionRecord value) { return ValidConsumedRecord(value); }
576
+ private static ConsumedIdentity Identity(AdmissionRecord value) { return new ConsumedIdentity { RequestId = value.RequestId, RootFingerprint = value.RootFingerprint, RequestDigest = value.RequestDigest, Token = value.Token, SessionId = value.SessionId, ListenerId = value.ListenerId, ListenerGeneration = value.ListenerGeneration, ExecutionDeadlineMs = value.ExecutionDeadlineMs, OwnerState = value.OwnerState }; }
577
+ private static bool Matches(AdmissionRecord value, ConsumedIdentity identity) { return MatchesIdentityFields(value, identity) && value.OwnerState == identity.OwnerState; }
578
+ private static bool MatchesIdentityFields(AdmissionRecord value, ConsumedIdentity identity)
579
+ {
580
+ return value != null && identity != null
581
+ && string.Equals(value.RequestId, identity.RequestId, StringComparison.Ordinal)
582
+ && string.Equals(value.RootFingerprint, identity.RootFingerprint, StringComparison.Ordinal)
583
+ && string.Equals(value.RequestDigest, identity.RequestDigest, StringComparison.Ordinal)
584
+ && FixedEquals(value.Token, identity.Token)
585
+ && string.Equals(value.SessionId, identity.SessionId, StringComparison.Ordinal)
586
+ && string.Equals(value.ListenerId, identity.ListenerId, StringComparison.Ordinal)
587
+ && value.ListenerGeneration == identity.ListenerGeneration
588
+ && value.ExecutionDeadlineMs == identity.ExecutionDeadlineMs;
589
+ }
400
590
  private static bool ValidListener(string listenerId, long generation) { return !string.IsNullOrWhiteSpace(listenerId) && listenerId.Length <= 200 && generation >= 1; }
401
591
 
402
592
  private static string NewToken()