From 16a81bdd304fba3c16657a7dd89abe55872c9bc0 Mon Sep 17 00:00:00 2001 From: Alexei Mochalov Date: Mon, 28 Sep 2026 11:45:04 +0200 Subject: [PATCH 1/2] Expose a role's workspace-role readiness in quilt3.admin Co-Authored-By: Claude Opus 5.5 (1M context) --- api/python/quilt3-graphql/queries.graphql | 2 + api/python/quilt3/_graphql_client/client.py | 44 +++++++++++++++++++ .../quilt3/_graphql_client/fragments.py | 2 + api/python/quilt3/admin/types.py | 2 + api/python/tests/test_admin_api.py | 4 ++ docs/CHANGELOG.md | 1 + docs/api-reference/Admin.md | 4 +- 7 files changed, 57 insertions(+), 2 deletions(-) diff --git a/api/python/quilt3-graphql/queries.graphql b/api/python/quilt3-graphql/queries.graphql index af2c5781c44..b5c93003d68 100644 --- a/api/python/quilt3-graphql/queries.graphql +++ b/api/python/quilt3-graphql/queries.graphql @@ -3,6 +3,7 @@ fragment UnmanagedRoleSelection on UnmanagedRole { id name arn + workspaceRoleReady } fragment PolicySummarySelection on Policy { id @@ -24,6 +25,7 @@ fragment ManagedRoleSelection on ManagedRole { id name arn + workspaceRoleReady policies { ...PolicySummarySelection } diff --git a/api/python/quilt3/_graphql_client/client.py b/api/python/quilt3/_graphql_client/client.py index aa0fb0e59b5..0b2aeabdf37 100644 --- a/api/python/quilt3/_graphql_client/client.py +++ b/api/python/quilt3/_graphql_client/client.py @@ -225,6 +225,7 @@ def roles_list(self, **kwargs: Any) -> List[Union[RolesListRolesUnmanagedRole, R id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -268,6 +269,7 @@ def roles_list(self, **kwargs: Any) -> List[Union[RolesListRolesUnmanagedRole, R id name arn + workspaceRoleReady } """ ) @@ -290,6 +292,7 @@ def role_get(self, id: str, **kwargs: Any) -> Optional[Union[RoleGetRoleUnmanage id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -333,6 +336,7 @@ def role_get(self, id: str, **kwargs: Any) -> Optional[Union[RoleGetRoleUnmanage id name arn + workspaceRoleReady } """ ) @@ -355,6 +359,7 @@ def policies_list(self, **kwargs: Any) -> List[PoliciesListPolicies]: id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -420,6 +425,7 @@ def policy_get(self, id: str, **kwargs: Any) -> Optional[PolicyGetPolicy]: id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -487,6 +493,7 @@ def default_role_get( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -530,6 +537,7 @@ def default_role_get( id name arn + workspaceRoleReady } """ ) @@ -565,6 +573,7 @@ def role_create_managed( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -608,6 +617,7 @@ def role_create_managed( id name arn + workspaceRoleReady } """ ) @@ -643,6 +653,7 @@ def role_create_unmanaged( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -686,6 +697,7 @@ def role_create_unmanaged( id name arn + workspaceRoleReady } """ ) @@ -724,6 +736,7 @@ def role_update_managed( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -767,6 +780,7 @@ def role_update_managed( id name arn + workspaceRoleReady } """ ) @@ -805,6 +819,7 @@ def role_update_unmanaged( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -848,6 +863,7 @@ def role_update_unmanaged( id name arn + workspaceRoleReady } """ ) @@ -905,6 +921,7 @@ def role_set_default( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -948,6 +965,7 @@ def role_set_default( id name arn + workspaceRoleReady } """ ) @@ -988,6 +1006,7 @@ def policy_create_managed( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1077,6 +1096,7 @@ def policy_create_unmanaged( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1166,6 +1186,7 @@ def policy_update_managed( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1255,6 +1276,7 @@ def policy_update_unmanaged( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1368,6 +1390,7 @@ def users_get(self, name: str, **kwargs: Any) -> Optional[UsersGetAdminUserGet]: id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1411,6 +1434,7 @@ def users_get(self, name: str, **kwargs: Any) -> Optional[UsersGetAdminUserGet]: id name arn + workspaceRoleReady } fragment UserSelection on User { @@ -1454,6 +1478,7 @@ def users_list(self, **kwargs: Any) -> List[UsersListAdminUserList]: id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1497,6 +1522,7 @@ def users_list(self, **kwargs: Any) -> List[UsersListAdminUserList]: id name arn + workspaceRoleReady } fragment UserSelection on User { @@ -1556,6 +1582,7 @@ def users_create( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1605,6 +1632,7 @@ def users_create( id name arn + workspaceRoleReady } fragment UserMutationSelection on UserResult { @@ -1704,6 +1732,7 @@ def users_set_email(self, email: str, name: str, **kwargs: Any) -> Optional[User id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1753,6 +1782,7 @@ def users_set_email(self, email: str, name: str, **kwargs: Any) -> Optional[User id name arn + workspaceRoleReady } fragment UserMutationSelection on UserResult { @@ -1814,6 +1844,7 @@ def users_set_admin(self, name: str, admin: bool, **kwargs: Any) -> Optional[Use id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1863,6 +1894,7 @@ def users_set_admin(self, name: str, admin: bool, **kwargs: Any) -> Optional[Use id name arn + workspaceRoleReady } fragment UserMutationSelection on UserResult { @@ -1924,6 +1956,7 @@ def users_set_active(self, active: bool, name: str, **kwargs: Any) -> Optional[U id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -1973,6 +2006,7 @@ def users_set_active(self, active: bool, name: str, **kwargs: Any) -> Optional[U id name arn + workspaceRoleReady } fragment UserMutationSelection on UserResult { @@ -2081,6 +2115,7 @@ def users_set_role( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -2130,6 +2165,7 @@ def users_set_role( id name arn + workspaceRoleReady } fragment UserSelection on User { @@ -2192,6 +2228,7 @@ def users_add_roles(self, name: str, roles: List[str], **kwargs: Any) -> Optiona id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -2241,6 +2278,7 @@ def users_add_roles(self, name: str, roles: List[str], **kwargs: Any) -> Optiona id name arn + workspaceRoleReady } fragment UserSelection on User { @@ -2300,6 +2338,7 @@ def users_remove_roles( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -2349,6 +2388,7 @@ def users_remove_roles( id name arn + workspaceRoleReady } fragment UserSelection on User { @@ -2394,6 +2434,7 @@ def sso_config_get(self, **kwargs: Any) -> Optional[SsoConfigGetAdminSsoConfig]: id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -2445,6 +2486,7 @@ def sso_config_get(self, **kwargs: Any) -> Optional[SsoConfigGetAdminSsoConfig]: id name arn + workspaceRoleReady } fragment UserSelection on User { @@ -2506,6 +2548,7 @@ def sso_config_set( id name arn + workspaceRoleReady policies { ...PolicySummarySelection } @@ -2563,6 +2606,7 @@ def sso_config_set( id name arn + workspaceRoleReady } fragment UserSelection on User { diff --git a/api/python/quilt3/_graphql_client/fragments.py b/api/python/quilt3/_graphql_client/fragments.py index 6bb8248c624..7b69b62b598 100644 --- a/api/python/quilt3/_graphql_client/fragments.py +++ b/api/python/quilt3/_graphql_client/fragments.py @@ -84,6 +84,7 @@ class ManagedRoleSelection(BaseModel): id: str name: str arn: str + workspace_role_ready: bool = Field(alias="workspaceRoleReady") policies: List["ManagedRoleSelectionPolicies"] permissions: List["ManagedRoleSelectionPermissions"] @@ -124,6 +125,7 @@ class UnmanagedRoleSelection(BaseModel): id: str name: str arn: str + workspace_role_ready: bool = Field(alias="workspaceRoleReady") class UserSelection(BaseModel): diff --git a/api/python/quilt3/admin/types.py b/api/python/quilt3/admin/types.py index 31321fc7e98..c4e8fc50ba6 100644 --- a/api/python/quilt3/admin/types.py +++ b/api/python/quilt3/admin/types.py @@ -43,6 +43,7 @@ class ManagedRole: id: str name: str arn: str + workspace_role_ready: bool policies: list[PolicySummary] permissions: list[Permission] typename__: T.Literal["ManagedRole"] @@ -53,6 +54,7 @@ class UnmanagedRole: id: str name: str arn: str + workspace_role_ready: bool typename__: T.Literal["UnmanagedRole"] diff --git a/api/python/tests/test_admin_api.py b/api/python/tests/test_admin_api.py index 889a0b11917..f6165851277 100644 --- a/api/python/tests/test_admin_api.py +++ b/api/python/tests/test_admin_api.py @@ -13,12 +13,14 @@ "id": "d7d15bef-c482-4086-ae6b-d0372b6145d2", "name": "UnmanagedRole", "arn": "arn:aws:iam::000000000000:role/UnmanagedRole", + "workspaceRoleReady": False, } MANAGED_ROLE = { "__typename": "ManagedRole", "id": "b1bab604-98fd-4b46-a20b-958cf2541c91", "name": "ManagedRole", "arn": "arn:aws:iam::000000000000:role/ManagedRole", + "workspaceRoleReady": True, "policies": [ { "id": "be8f3af1-8f5b-463a-89ff-373769e6d0d3", @@ -48,6 +50,7 @@ id="b1bab604-98fd-4b46-a20b-958cf2541c91", name="ManagedRole", arn="arn:aws:iam::000000000000:role/ManagedRole", + workspace_role_ready=True, policies=[EXPECTED_POLICY_SUMMARY], permissions=[EXPECTED_PERMISSION], typename__="ManagedRole", @@ -56,6 +59,7 @@ id="d7d15bef-c482-4086-ae6b-d0372b6145d2", name="UnmanagedRole", arn="arn:aws:iam::000000000000:role/UnmanagedRole", + workspace_role_ready=False, typename__="UnmanagedRole", ) POLICY = { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1b9b4388224..e932456baae 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -25,6 +25,7 @@ Entries inside each section should be ordered by type: ### Python API +* [Added] `quilt3.admin.ManagedRole` and `UnmanagedRole` expose `workspace_role_ready`: whether the role's workspace IAM role is ready to vend sessions on. Calls that return roles, users or policies require a registry that serves it * [Added] `quilt3.admin.RoleOwnsDataProductsError` — `admin.roles.delete()` raises it when the registry refuses to delete a role that owns data products. Earlier clients, whose generated result union does not carry the type, fail that response with an unhandled pydantic `ValidationError` ([#5299](https://github.com/quiltdata/quilt/pull/5299)) * [Added] `Package.browse()` and `Package.install()` accept Catalog `quilt+s3://` package, directory, and entry URIs, including latest, named-tag, and hash selectors. A directory URI may carry the trailing slash the Catalog emits (`&path=baz%2F`), a path that descends through an entry (`&path=foo.csv%2Fbar`) reports that the package does not contain it rather than raising `AttributeError`, a raw `+` in an unencoded path stays a `+` instead of becoming a space, and the scheme is matched case-insensitively ([#5255](https://github.com/quiltdata/quilt/pull/5255)) * [Added] `quilt3.session.use_registry_url()`, `set_registry_url_resolver()`, and `reset_registry_url_resolver()` for overriding the registry URL in-process without writing `~/.quilt/config.yml`, so a host application can drive a registry chosen at runtime or several registries in one process; each operation uses one URL snapshot, and authenticated HTTP-session creation is synchronized and isolated per resolved registry URL ([#5240](https://github.com/quiltdata/quilt/pull/5240)) diff --git a/docs/api-reference/Admin.md b/docs/api-reference/Admin.md index 1f2189f29df..b4ac5d18a9f 100644 --- a/docs/api-reference/Admin.md +++ b/docs/api-reference/Admin.md @@ -8,10 +8,10 @@ ## PolicySummary(id: str, title: str, arn: str, managed: bool, permissions: list[quilt3.admin.types.Permission]) -> None {#PolicySummary} Policy without back-references to roles (avoids circular nesting). -## ManagedRole(id: str, name: str, arn: str, policies: list[quilt3.admin.types.PolicySummary], permissions: list[quilt3.admin.types.Permission], typename\_\_: Literal['ManagedRole']) -> None {#ManagedRole} +## ManagedRole(id: str, name: str, arn: str, workspace\_role\_ready: bool, policies: list[quilt3.admin.types.PolicySummary], permissions: list[quilt3.admin.types.Permission], typename\_\_: Literal['ManagedRole']) -> None {#ManagedRole} -## UnmanagedRole(id: str, name: str, arn: str, typename\_\_: Literal['UnmanagedRole']) -> None {#UnmanagedRole} +## UnmanagedRole(id: str, name: str, arn: str, workspace\_role\_ready: bool, typename\_\_: Literal['UnmanagedRole']) -> None {#UnmanagedRole} ## Policy(id: str, title: str, arn: str, managed: bool, permissions: list[quilt3.admin.types.Permission], roles: list[quilt3.admin.types.ManagedRole]) -> None {#Policy} From 375d909d86099d53a0c16b4b9d0d4c5a8eed261d Mon Sep 17 00:00:00 2001 From: Alexei Mochalov Date: Mon, 28 Sep 2026 11:47:17 +0200 Subject: [PATCH 2/2] Link the changelog entry to its pull request Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index e932456baae..e90fdb4b2de 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -25,7 +25,7 @@ Entries inside each section should be ordered by type: ### Python API -* [Added] `quilt3.admin.ManagedRole` and `UnmanagedRole` expose `workspace_role_ready`: whether the role's workspace IAM role is ready to vend sessions on. Calls that return roles, users or policies require a registry that serves it +* [Added] `quilt3.admin.ManagedRole` and `UnmanagedRole` expose `workspace_role_ready`: whether the role's workspace IAM role is ready to vend sessions on. Calls that return roles, users or policies require a registry that serves it ([#5362](https://github.com/quiltdata/quilt/pull/5362)) * [Added] `quilt3.admin.RoleOwnsDataProductsError` — `admin.roles.delete()` raises it when the registry refuses to delete a role that owns data products. Earlier clients, whose generated result union does not carry the type, fail that response with an unhandled pydantic `ValidationError` ([#5299](https://github.com/quiltdata/quilt/pull/5299)) * [Added] `Package.browse()` and `Package.install()` accept Catalog `quilt+s3://` package, directory, and entry URIs, including latest, named-tag, and hash selectors. A directory URI may carry the trailing slash the Catalog emits (`&path=baz%2F`), a path that descends through an entry (`&path=foo.csv%2Fbar`) reports that the package does not contain it rather than raising `AttributeError`, a raw `+` in an unencoded path stays a `+` instead of becoming a space, and the scheme is matched case-insensitively ([#5255](https://github.com/quiltdata/quilt/pull/5255)) * [Added] `quilt3.session.use_registry_url()`, `set_registry_url_resolver()`, and `reset_registry_url_resolver()` for overriding the registry URL in-process without writing `~/.quilt/config.yml`, so a host application can drive a registry chosen at runtime or several registries in one process; each operation uses one URL snapshot, and authenticated HTTP-session creation is synchronized and isolated per resolved registry URL ([#5240](https://github.com/quiltdata/quilt/pull/5240))