)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"533dc64e_c5a6a813","updated":"2026-07-21 16:16:32.000000000","message":"mostly nits","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":9914,"name":"Ade Lee","email":"alee@redhat.com","username":"alee"},"change_message_id":"7290175b40cb8bfa1c9c46aef22ea0901bd0fa6b","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"ff097958_16453320","updated":"2026-08-19 20:38:24.000000000","message":"Minor nit, but in general, looks ok to me.","commit_id":"1d0df9326bd7d066548dfa4d27ffa2ad67632188"}],"specs/keystone/2026.2/explicit-project-domain-ids.rst":[{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":90,"context_line":"* The field is consumed during creation and does not appear as a separate"},{"line_number":91,"context_line":"  attribute in the response body — the resulting entity\u0027s ``id`` field"},{"line_number":92,"context_line":"  contains the value, as with auto-generated IDs."},{"line_number":93,"context_line":"* For both projects and domains: ``id`` accepts a UUID in dashless hex form"},{"line_number":94,"context_line":"  only (``xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx``, 32 lowercase hexadecimal"},{"line_number":95,"context_line":"  characters). This matches the format auto-generated IDs use internally and"},{"line_number":96,"context_line":"  the format returned in all API responses, avoiding confusion about output"}],"source_content_type":"text/x-rst","patch_set":2,"id":"2b5effc2_2a7f8e2a","line":93,"updated":"2026-07-21 16:16:32.000000000","message":"I would suggest to stress, that this is required to keep Fernet roundtrip happy. Otherwise it may be forgotten","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":90,"context_line":"* The field is consumed during creation and does not appear as a separate"},{"line_number":91,"context_line":"  attribute in the response body — the resulting entity\u0027s ``id`` field"},{"line_number":92,"context_line":"  contains the value, as with auto-generated IDs."},{"line_number":93,"context_line":"* For both projects and domains: ``id`` accepts a UUID in dashless hex form"},{"line_number":94,"context_line":"  only (``xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx``, 32 lowercase hexadecimal"},{"line_number":95,"context_line":"  characters). This matches the format auto-generated IDs use internally and"},{"line_number":96,"context_line":"  the format returned in all API responses, avoiding confusion about output"}],"source_content_type":"text/x-rst","patch_set":2,"id":"6e36c263_49f903e8","line":93,"in_reply_to":"2b5effc2_2a7f8e2a","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":118,"context_line":"ID Format Constraints"},{"line_number":119,"context_line":"---------------------"},{"line_number":120,"context_line":""},{"line_number":121,"context_line":"A primary use case for this feature is enabling operators to assign IDs that"},{"line_number":122,"context_line":"match identifiers already used in an external Identity Provider (IdP) —"},{"line_number":123,"context_line":"typically Active Directory (AD), an LDAP directory, or Microsoft Entra ID"},{"line_number":124,"context_line":"(formerly Azure AD).  Understanding the ID formats these systems use is"}],"source_content_type":"text/x-rst","patch_set":2,"id":"44f6334c_85f1ca9c","line":121,"updated":"2026-07-21 16:16:32.000000000","message":"It feels to me that a reference to ldap/ad/entra/idp is confusing more than it clarifies. For user_id - sure, for project/domain IDs it is not relevant. The only reason for us to force certain format is the fernet de-serialization expectations","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":118,"context_line":"ID Format Constraints"},{"line_number":119,"context_line":"---------------------"},{"line_number":120,"context_line":""},{"line_number":121,"context_line":"A primary use case for this feature is enabling operators to assign IDs that"},{"line_number":122,"context_line":"match identifiers already used in an external Identity Provider (IdP) —"},{"line_number":123,"context_line":"typically Active Directory (AD), an LDAP directory, or Microsoft Entra ID"},{"line_number":124,"context_line":"(formerly Azure AD).  Understanding the ID formats these systems use is"}],"source_content_type":"text/x-rst","patch_set":2,"id":"dbd5c853_0a46d1eb","line":121,"in_reply_to":"44f6334c_85f1ca9c","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":169,"context_line":"| Database        | ``String(64)`` -- hard maximum of 64 characters.          |"},{"line_number":170,"context_line":"+-----------------+-----------------------------------------------------------+"},{"line_number":171,"context_line":""},{"line_number":172,"context_line":"**Current behaviour**: The runtime check enforces dashless hex format (32"},{"line_number":173,"context_line":"lowercase hexadecimal characters).  Callers must strip dashes from AD"},{"line_number":174,"context_line":"``objectGUID`` or Entra ID ``id`` values before passing them to the API."},{"line_number":175,"context_line":""}],"source_content_type":"text/x-rst","patch_set":2,"id":"0786b89b_75fa4f1e","line":172,"updated":"2026-07-21 16:16:32.000000000","message":"this should not be relevant for projects/domains","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":169,"context_line":"| Database        | ``String(64)`` -- hard maximum of 64 characters.          |"},{"line_number":170,"context_line":"+-----------------+-----------------------------------------------------------+"},{"line_number":171,"context_line":""},{"line_number":172,"context_line":"**Current behaviour**: The runtime check enforces dashless hex format (32"},{"line_number":173,"context_line":"lowercase hexadecimal characters).  Callers must strip dashes from AD"},{"line_number":174,"context_line":"``objectGUID`` or Entra ID ``id`` values before passing them to the API."},{"line_number":175,"context_line":""}],"source_content_type":"text/x-rst","patch_set":2,"id":"112fcb5a_1dec40d1","line":172,"in_reply_to":"0786b89b_75fa4f1e","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":200,"context_line":"|                 | ``String(64)`` -- hard maximum of 64 characters.          |"},{"line_number":201,"context_line":"+-----------------+-----------------------------------------------------------+"},{"line_number":202,"context_line":""},{"line_number":203,"context_line":"**Expected from IdP systems**: AD ``objectGUID``, Entra ID ``id``, and LDAP"},{"line_number":204,"context_line":"``entryUUID`` values are all dashed UUIDs (36 characters).  These are the"},{"line_number":205,"context_line":"natural identifiers an operator would want to re-use for a project that"},{"line_number":206,"context_line":"represents a tenant in their directory."}],"source_content_type":"text/x-rst","patch_set":2,"id":"6eddc687_b70a1291","line":203,"updated":"2026-07-21 16:16:32.000000000","message":"also here - not relevant","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":200,"context_line":"|                 | ``String(64)`` -- hard maximum of 64 characters.          |"},{"line_number":201,"context_line":"+-----------------+-----------------------------------------------------------+"},{"line_number":202,"context_line":""},{"line_number":203,"context_line":"**Expected from IdP systems**: AD ``objectGUID``, Entra ID ``id``, and LDAP"},{"line_number":204,"context_line":"``entryUUID`` values are all dashed UUIDs (36 characters).  These are the"},{"line_number":205,"context_line":"natural identifiers an operator would want to re-use for a project that"},{"line_number":206,"context_line":"represents a tenant in their directory."}],"source_content_type":"text/x-rst","patch_set":2,"id":"356db1be_f0c5583a","line":203,"in_reply_to":"6eddc687_b70a1291","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":246,"context_line":"  ``allow_explicit_project_id_at_creation`` was proposed in [1]_. This is a"},{"line_number":247,"context_line":"  binary on/off that cannot be scoped to specific callers and introduces"},{"line_number":248,"context_line":"  deployer operational complexity without providing finer-grained control."},{"line_number":249,"context_line":"  This spec instead uses RBAC to provide acces to administrators only."},{"line_number":250,"context_line":"  Domains use existing ``identity:create_domain`` (already admin-only)."},{"line_number":251,"context_line":""},{"line_number":252,"context_line":"* **Using ``explicit_project_id`` instead of ``id``.** The existing domain"}],"source_content_type":"text/x-rst","patch_set":2,"id":"b32f39c7_0fcc99d3","line":249,"updated":"2026-07-21 16:16:32.000000000","message":"\"access\"","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":246,"context_line":"  ``allow_explicit_project_id_at_creation`` was proposed in [1]_. This is a"},{"line_number":247,"context_line":"  binary on/off that cannot be scoped to specific callers and introduces"},{"line_number":248,"context_line":"  deployer operational complexity without providing finer-grained control."},{"line_number":249,"context_line":"  This spec instead uses RBAC to provide acces to administrators only."},{"line_number":250,"context_line":"  Domains use existing ``identity:create_domain`` (already admin-only)."},{"line_number":251,"context_line":""},{"line_number":252,"context_line":"* **Using ``explicit_project_id`` instead of ``id``.** The existing domain"}],"source_content_type":"text/x-rst","patch_set":2,"id":"44cd650f_beb0f91c","line":249,"in_reply_to":"b32f39c7_0fcc99d3","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":269,"context_line":"* **Federation / Keystone-to-Keystone.** K2K federation can share"},{"line_number":270,"context_line":"  authentication state across regions but requires trust relationships between"},{"line_number":271,"context_line":"  deployments and does not give the operator control over which UUID a project"},{"line_number":272,"context_line":"  has in each region\u0027s local database."},{"line_number":273,"context_line":""},{"line_number":274,"context_line":""},{"line_number":275,"context_line":"Security Impact"}],"source_content_type":"text/x-rst","patch_set":2,"id":"3bd9bb9c_95517ee9","line":272,"updated":"2026-07-21 16:16:32.000000000","message":"and is most likely be deprecated in the soon future (due to drop of saml)","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":294,"context_line":"  will be ``None`` when the ID is not provided.  The policy will be updated"},{"line_number":295,"context_line":"  to ensure there is no predefined value for ID when the request is being made"},{"line_number":296,"context_line":"  by a manager.  i.e."},{"line_number":297,"context_line":"  ``role:admin or (role:manager and target.project.id:None)``"},{"line_number":298,"context_line":"  This prevents domain managers from creating projects with"},{"line_number":299,"context_line":"  arbitrary IDs, which could enable resource theft via ID collision."},{"line_number":300,"context_line":""}],"source_content_type":"text/x-rst","patch_set":2,"id":"efb7685e_1f3e5ba4","line":297,"updated":"2026-07-21 16:16:32.000000000","message":"need to ensure explicit_project_id and id are merged together BEFORE evaluating policy","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":478,"context_line":"   .. code-block:: python"},{"line_number":479,"context_line":""},{"line_number":480,"context_line":"       # In parameter_types.py, define:"},{"line_number":481,"context_line":"       explicit_id \u003d {"},{"line_number":482,"context_line":"           \"type\": \"string\","},{"line_number":483,"context_line":"           \"pattern\": \"^[0-9a-f]{32}$\","},{"line_number":484,"context_line":"           \"minLength\": 32,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"e9d123ee_d20ced49","line":481,"updated":"2026-07-21 16:16:32.000000000","message":"I would place it into keystone/common/validation/parameter_types.py for proper future reuse. Actually the code below already assumes this","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":9914,"name":"Ade Lee","email":"alee@redhat.com","username":"alee"},"change_message_id":"7290175b40cb8bfa1c9c46aef22ea0901bd0fa6b","unresolved":true,"context_lines":[{"line_number":478,"context_line":"   .. code-block:: python"},{"line_number":479,"context_line":""},{"line_number":480,"context_line":"       # In parameter_types.py, define:"},{"line_number":481,"context_line":"       explicit_id \u003d {"},{"line_number":482,"context_line":"           \"type\": \"string\","},{"line_number":483,"context_line":"           \"pattern\": \"^[0-9a-f]{32}$\","},{"line_number":484,"context_line":"           \"minLength\": 32,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"d62891d0_d2f1ece8","line":481,"in_reply_to":"cf33b48c_f4d4fe75","updated":"2026-08-19 20:38:24.000000000","message":"This is perhaps a detail that can be hashed out on the implementation patches.","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"dd40fcbd8e06c5c7756ea1afb6c44015b04f2d7d","unresolved":true,"context_lines":[{"line_number":478,"context_line":"   .. code-block:: python"},{"line_number":479,"context_line":""},{"line_number":480,"context_line":"       # In parameter_types.py, define:"},{"line_number":481,"context_line":"       explicit_id \u003d {"},{"line_number":482,"context_line":"           \"type\": \"string\","},{"line_number":483,"context_line":"           \"pattern\": \"^[0-9a-f]{32}$\","},{"line_number":484,"context_line":"           \"minLength\": 32,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"cf33b48c_f4d4fe75","line":481,"in_reply_to":"e9d123ee_d20ced49","updated":"2026-08-18 13:48:56.000000000","message":"I believe this path is deprecated as it shows on the import:\n`from keystone.common.validation import parameter_types as old_parameter_types`\nhttps://github.com/openstack/keystone/blob/master/keystone/resource/schema.py#L19\nInstead I suggested it here: keystone/api/validation/parameter_types.py","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":7973,"name":"Douglas Mendizábal","email":"dmendiza@redhat.com","username":"dougmendizabal"},"change_message_id":"8cbbfd48c8b59d8f8b2e732b0898fdf3421fab8f","unresolved":true,"context_lines":[{"line_number":508,"context_line":"                       **_domain_properties,"},{"line_number":509,"context_line":"                   },"},{"line_number":510,"context_line":"                   \"required\": [\"name\"],"},{"line_number":511,"context_line":"                   \"oneOf\": ["},{"line_number":512,"context_line":"                       {"},{"line_number":513,"context_line":"                           \"not\": {"},{"line_number":514,"context_line":"                               \"required\": [\"id\", \"explicti_domain_id\"]"},{"line_number":515,"context_line":"                           }"},{"line_number":516,"context_line":"                       }"},{"line_number":517,"context_line":"                   ]"},{"line_number":518,"context_line":"               }"},{"line_number":519,"context_line":"           },"},{"line_number":520,"context_line":"           \"additionalProperties\": False,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"634c4e99_87a57ed1","line":517,"range":{"start_line":511,"start_character":19,"end_line":517,"end_character":20},"updated":"2026-07-15 17:36:32.000000000","message":"The previous version of this spec used in-code checks for this, but I think it\u0027s cleaner to do it in the schema validation.","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":508,"context_line":"                       **_domain_properties,"},{"line_number":509,"context_line":"                   },"},{"line_number":510,"context_line":"                   \"required\": [\"name\"],"},{"line_number":511,"context_line":"                   \"oneOf\": ["},{"line_number":512,"context_line":"                       {"},{"line_number":513,"context_line":"                           \"not\": {"},{"line_number":514,"context_line":"                               \"required\": [\"id\", \"explicti_domain_id\"]"},{"line_number":515,"context_line":"                           }"},{"line_number":516,"context_line":"                       }"},{"line_number":517,"context_line":"                   ]"},{"line_number":518,"context_line":"               }"},{"line_number":519,"context_line":"           },"},{"line_number":520,"context_line":"           \"additionalProperties\": False,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"ec1ae071_3e0585c4","line":517,"range":{"start_line":511,"start_character":19,"end_line":517,"end_character":20},"in_reply_to":"39822e33_5770e4d7","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":508,"context_line":"                       **_domain_properties,"},{"line_number":509,"context_line":"                   },"},{"line_number":510,"context_line":"                   \"required\": [\"name\"],"},{"line_number":511,"context_line":"                   \"oneOf\": ["},{"line_number":512,"context_line":"                       {"},{"line_number":513,"context_line":"                           \"not\": {"},{"line_number":514,"context_line":"                               \"required\": [\"id\", \"explicti_domain_id\"]"},{"line_number":515,"context_line":"                           }"},{"line_number":516,"context_line":"                       }"},{"line_number":517,"context_line":"                   ]"},{"line_number":518,"context_line":"               }"},{"line_number":519,"context_line":"           },"},{"line_number":520,"context_line":"           \"additionalProperties\": False,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"39822e33_5770e4d7","line":517,"range":{"start_line":511,"start_character":19,"end_line":517,"end_character":20},"in_reply_to":"634c4e99_87a57ed1","updated":"2026-07-21 16:16:32.000000000","message":"definitely better to do it in the schema. nit: typo \"explicti_domain_id\". But I think the proper schema should be `not: { \"allOf\": [{ \"required\": [\"id\"] }, { \"required\": [\"explicit_domain_id\"] }] }`","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":558,"context_line":""},{"line_number":559,"context_line":"   .. code-block:: python"},{"line_number":560,"context_line":""},{"line_number":561,"context_line":"       project_id \u003d project.pop(\u0027id\u0027, None)"},{"line_number":562,"context_line":"       if project_id is None:"},{"line_number":563,"context_line":"           project \u003d self._assign_unique_id(project)"},{"line_number":564,"context_line":"       else:"}],"source_content_type":"text/x-rst","patch_set":2,"id":"9cd4782f_00d3ef2a","line":561,"updated":"2026-07-21 16:16:32.000000000","message":"weird construct of doing pop and re-insert. Shouldn\u0027t following be sufficient?\n```\nif project.get(\u0027id\u0027) is None:\n    project \u003d self._assign_unique_id(project)\n```","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":558,"context_line":""},{"line_number":559,"context_line":"   .. code-block:: python"},{"line_number":560,"context_line":""},{"line_number":561,"context_line":"       project_id \u003d project.pop(\u0027id\u0027, None)"},{"line_number":562,"context_line":"       if project_id is None:"},{"line_number":563,"context_line":"           project \u003d self._assign_unique_id(project)"},{"line_number":564,"context_line":"       else:"}],"source_content_type":"text/x-rst","patch_set":2,"id":"e6458bb5_d95e5b69","line":561,"in_reply_to":"9cd4782f_00d3ef2a","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":7973,"name":"Douglas Mendizábal","email":"dmendiza@redhat.com","username":"dougmendizabal"},"change_message_id":"8cbbfd48c8b59d8f8b2e732b0898fdf3421fab8f","unresolved":true,"context_lines":[{"line_number":604,"context_line":"            \"\"\"Verify ID doesn\u0027t exist in projects, users, or domains.\"\"\""},{"line_number":605,"context_line":"            conflicts \u003d []"},{"line_number":606,"context_line":""},{"line_number":607,"context_line":"            if resource_type !\u003d \u0027project\u0027:"},{"line_number":608,"context_line":"                try:"},{"line_number":609,"context_line":"                    PROVIDERS.resource_api.get_project(resource_id)"},{"line_number":610,"context_line":"                    conflicts.append(\u0027project\u0027)"},{"line_number":611,"context_line":"                except exception.ProjectNotFound:"},{"line_number":612,"context_line":"                    pass"},{"line_number":613,"context_line":""},{"line_number":614,"context_line":"            if resource_type !\u003d \u0027user\u0027:"},{"line_number":615,"context_line":"                try:"},{"line_number":616,"context_line":"                    PROVIDERS.identity_api.get_user(resource_id)"},{"line_number":617,"context_line":"                    conflicts.append(\u0027user\u0027)"},{"line_number":618,"context_line":"                except exception.UserNotFound:"},{"line_number":619,"context_line":"                    pass"},{"line_number":620,"context_line":""},{"line_number":621,"context_line":"            if resource_type !\u003d \u0027domain\u0027:"},{"line_number":622,"context_line":"                try:"},{"line_number":623,"context_line":"                    PROVIDERS.resource_api.get_domain(resource_id)"},{"line_number":624,"context_line":"                    conflicts.append(\u0027domain\u0027)"},{"line_number":625,"context_line":"                except exception.DomainNotFound:"},{"line_number":626,"context_line":"                    pass"},{"line_number":627,"context_line":""},{"line_number":628,"context_line":"            if conflicts:"},{"line_number":629,"context_line":"                raise exception.Conflict("}],"source_content_type":"text/x-rst","patch_set":2,"id":"ca7e8fd7_80397120","line":626,"range":{"start_line":607,"start_character":12,"end_line":626,"end_character":24},"updated":"2026-07-15 17:36:32.000000000","message":"Given that domains are just projects with a flag set, and are both stored in the same table, I wonder if there is a better/more efficient way to check for uniqueness?\n\nWe may even be able to just check against the user table since trying to insert either project or domain into the project table would result in a conflict error at the database layer anyway.","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":604,"context_line":"            \"\"\"Verify ID doesn\u0027t exist in projects, users, or domains.\"\"\""},{"line_number":605,"context_line":"            conflicts \u003d []"},{"line_number":606,"context_line":""},{"line_number":607,"context_line":"            if resource_type !\u003d \u0027project\u0027:"},{"line_number":608,"context_line":"                try:"},{"line_number":609,"context_line":"                    PROVIDERS.resource_api.get_project(resource_id)"},{"line_number":610,"context_line":"                    conflicts.append(\u0027project\u0027)"},{"line_number":611,"context_line":"                except exception.ProjectNotFound:"},{"line_number":612,"context_line":"                    pass"},{"line_number":613,"context_line":""},{"line_number":614,"context_line":"            if resource_type !\u003d \u0027user\u0027:"},{"line_number":615,"context_line":"                try:"},{"line_number":616,"context_line":"                    PROVIDERS.identity_api.get_user(resource_id)"},{"line_number":617,"context_line":"                    conflicts.append(\u0027user\u0027)"},{"line_number":618,"context_line":"                except exception.UserNotFound:"},{"line_number":619,"context_line":"                    pass"},{"line_number":620,"context_line":""},{"line_number":621,"context_line":"            if resource_type !\u003d \u0027domain\u0027:"},{"line_number":622,"context_line":"                try:"},{"line_number":623,"context_line":"                    PROVIDERS.resource_api.get_domain(resource_id)"},{"line_number":624,"context_line":"                    conflicts.append(\u0027domain\u0027)"},{"line_number":625,"context_line":"                except exception.DomainNotFound:"},{"line_number":626,"context_line":"                    pass"},{"line_number":627,"context_line":""},{"line_number":628,"context_line":"            if conflicts:"},{"line_number":629,"context_line":"                raise exception.Conflict("}],"source_content_type":"text/x-rst","patch_set":2,"id":"ce7307d6_e2048237","line":626,"range":{"start_line":607,"start_character":12,"end_line":626,"end_character":24},"in_reply_to":"c6226928_bbac0a31","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":604,"context_line":"            \"\"\"Verify ID doesn\u0027t exist in projects, users, or domains.\"\"\""},{"line_number":605,"context_line":"            conflicts \u003d []"},{"line_number":606,"context_line":""},{"line_number":607,"context_line":"            if resource_type !\u003d \u0027project\u0027:"},{"line_number":608,"context_line":"                try:"},{"line_number":609,"context_line":"                    PROVIDERS.resource_api.get_project(resource_id)"},{"line_number":610,"context_line":"                    conflicts.append(\u0027project\u0027)"},{"line_number":611,"context_line":"                except exception.ProjectNotFound:"},{"line_number":612,"context_line":"                    pass"},{"line_number":613,"context_line":""},{"line_number":614,"context_line":"            if resource_type !\u003d \u0027user\u0027:"},{"line_number":615,"context_line":"                try:"},{"line_number":616,"context_line":"                    PROVIDERS.identity_api.get_user(resource_id)"},{"line_number":617,"context_line":"                    conflicts.append(\u0027user\u0027)"},{"line_number":618,"context_line":"                except exception.UserNotFound:"},{"line_number":619,"context_line":"                    pass"},{"line_number":620,"context_line":""},{"line_number":621,"context_line":"            if resource_type !\u003d \u0027domain\u0027:"},{"line_number":622,"context_line":"                try:"},{"line_number":623,"context_line":"                    PROVIDERS.resource_api.get_domain(resource_id)"},{"line_number":624,"context_line":"                    conflicts.append(\u0027domain\u0027)"},{"line_number":625,"context_line":"                except exception.DomainNotFound:"},{"line_number":626,"context_line":"                    pass"},{"line_number":627,"context_line":""},{"line_number":628,"context_line":"            if conflicts:"},{"line_number":629,"context_line":"                raise exception.Conflict("}],"source_content_type":"text/x-rst","patch_set":2,"id":"c6226928_bbac0a31","line":626,"range":{"start_line":607,"start_character":12,"end_line":626,"end_character":24},"in_reply_to":"ca7e8fd7_80397120","updated":"2026-07-21 16:16:32.000000000","message":"get_project \"should\" return also domain with `is_domain\u003dTrue`. I do not see any filters cleaning up results","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":637,"context_line":"   .. code-block:: python"},{"line_number":638,"context_line":""},{"line_number":639,"context_line":"       project \u003d self.request_body_json.get(\u0027project\u0027, {})"},{"line_number":640,"context_line":"       target \u003d {\u0027project\u0027: project}"},{"line_number":641,"context_line":""},{"line_number":642,"context_line":"       ENFORCER.enforce_call("},{"line_number":643,"context_line":"           action\u003d\u0027identity:create_project\u0027,"}],"source_content_type":"text/x-rst","patch_set":2,"id":"03998ff2_b0e422ef","line":640,"updated":"2026-07-21 16:16:32.000000000","message":"the merge of explicit_project_id and id must happen here before calling policy","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":722,"context_line":"    * ``test_cannot_create_domain_with_existing_project_id`` — Reverse check"},{"line_number":723,"context_line":"    * ``test_cannot_create_project_with_existing_user_id`` - Creating a"},{"line_number":724,"context_line":"      project with an ID that matches an existing user returns 409 Conflict"},{"line_number":725,"context_line":"    * ``test_cannot_create_domain_with_existing_user_id``` - Same check for"},{"line_number":726,"context_line":"      domains."},{"line_number":727,"context_line":"    * ``test_can_create_resources_with_unique_ids`` — Verify that different"},{"line_number":728,"context_line":"      IDs work fine"}],"source_content_type":"text/x-rst","patch_set":2,"id":"ada1e34c_c86ada85","line":725,"updated":"2026-07-21 16:16:32.000000000","message":"one backtick too much","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":722,"context_line":"    * ``test_cannot_create_domain_with_existing_project_id`` — Reverse check"},{"line_number":723,"context_line":"    * ``test_cannot_create_project_with_existing_user_id`` - Creating a"},{"line_number":724,"context_line":"      project with an ID that matches an existing user returns 409 Conflict"},{"line_number":725,"context_line":"    * ``test_cannot_create_domain_with_existing_user_id``` - Same check for"},{"line_number":726,"context_line":"      domains."},{"line_number":727,"context_line":"    * ``test_can_create_resources_with_unique_ids`` — Verify that different"},{"line_number":728,"context_line":"      IDs work fine"}],"source_content_type":"text/x-rst","patch_set":2,"id":"7918846a_38dbd089","line":725,"in_reply_to":"ada1e34c_c86ada85","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":27900,"name":"Artem Goncharov","email":"artem.goncharov@gmail.com","username":"gtema"},"change_message_id":"423ee81ba51224ea1ba5eb8888652975e7d2053f","unresolved":true,"context_lines":[{"line_number":784,"context_line":"    * ``ProjectManager.create(..., id\u003dNone)`` — passes ``id`` in the request"},{"line_number":785,"context_line":"      body when supplied."},{"line_number":786,"context_line":"    * ``DomainManager.create(..., id\u003dNone)`` — passes ``id`` in the request"},{"line_number":787,"context_line":"      body when supplied. For backwards compatibilit still accept"},{"line_number":788,"context_line":"      ``explicit_domain_id``, but raise ``ValueError`` if both are supplied."},{"line_number":789,"context_line":""},{"line_number":790,"context_line":"    Unit tests (``keystoneclient/tests/unit/v3/``):"}],"source_content_type":"text/x-rst","patch_set":2,"id":"6b834f90_451746e4","line":787,"updated":"2026-07-21 16:16:32.000000000","message":"\"compatibility\"","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":784,"context_line":"    * ``ProjectManager.create(..., id\u003dNone)`` — passes ``id`` in the request"},{"line_number":785,"context_line":"      body when supplied."},{"line_number":786,"context_line":"    * ``DomainManager.create(..., id\u003dNone)`` — passes ``id`` in the request"},{"line_number":787,"context_line":"      body when supplied. For backwards compatibilit still accept"},{"line_number":788,"context_line":"      ``explicit_domain_id``, but raise ``ValueError`` if both are supplied."},{"line_number":789,"context_line":""},{"line_number":790,"context_line":"    Unit tests (``keystoneclient/tests/unit/v3/``):"}],"source_content_type":"text/x-rst","patch_set":2,"id":"5144c667_7b523cf3","line":787,"in_reply_to":"6b834f90_451746e4","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":7973,"name":"Douglas Mendizábal","email":"dmendiza@redhat.com","username":"dougmendizabal"},"change_message_id":"8cbbfd48c8b59d8f8b2e732b0898fdf3421fab8f","unresolved":true,"context_lines":[{"line_number":906,"context_line":"6. **Implement purge job** to permanently delete soft-deleted resources"},{"line_number":907,"context_line":"   after a configurable retention period (default 90 days)."},{"line_number":908,"context_line":""},{"line_number":909,"context_line":"7. **Modify uniqueness constraints** to allow duplicate names when one"},{"line_number":910,"context_line":"   instance is soft-deleted:"},{"line_number":911,"context_line":""},{"line_number":912,"context_line":"   * PostgreSQL: Partial unique indexes (``WHERE deleted \u003d False``)"},{"line_number":913,"context_line":"   * MySQL: Generated columns (``name_active \u003d IF(deleted, NULL, name)``)"},{"line_number":914,"context_line":""}],"source_content_type":"text/x-rst","patch_set":2,"id":"9eb280a6_046045e3","line":911,"range":{"start_line":909,"start_character":3,"end_line":911,"end_character":1},"updated":"2026-07-15 17:36:32.000000000","message":"We may be going too in-depth in this section.  This stuff should probably be ironed out in the soft-delete spec instead of trying to outline the process in this spec.\n\nFor example, it\u0027s unclear to me whether un-deleting a soft-deleted project would be allowed.  Technically it would just be a change to the value of the ``deleted`` column, but it would create issues for this kind of exception that is made when objects are in the soft-deleted state.","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":906,"context_line":"6. **Implement purge job** to permanently delete soft-deleted resources"},{"line_number":907,"context_line":"   after a configurable retention period (default 90 days)."},{"line_number":908,"context_line":""},{"line_number":909,"context_line":"7. **Modify uniqueness constraints** to allow duplicate names when one"},{"line_number":910,"context_line":"   instance is soft-deleted:"},{"line_number":911,"context_line":""},{"line_number":912,"context_line":"   * PostgreSQL: Partial unique indexes (``WHERE deleted \u003d False``)"},{"line_number":913,"context_line":"   * MySQL: Generated columns (``name_active \u003d IF(deleted, NULL, name)``)"},{"line_number":914,"context_line":""}],"source_content_type":"text/x-rst","patch_set":2,"id":"b21e72fc_433513de","line":911,"range":{"start_line":909,"start_character":3,"end_line":911,"end_character":1},"in_reply_to":"9eb280a6_046045e3","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"6dc029a34bbbe4c95ef2b6ab608b86fc822c4023"},{"author":{"_account_id":9914,"name":"Ade Lee","email":"alee@redhat.com","username":"alee"},"change_message_id":"7290175b40cb8bfa1c9c46aef22ea0901bd0fa6b","unresolved":true,"context_lines":[{"line_number":279,"context_line":"Global ID Uniqueness"},{"line_number":280,"context_line":"~~~~~~~~~~~~~~~~~~~~"},{"line_number":281,"context_line":""},{"line_number":282,"context_line":"Resource IDs must be unique across all resource types (projects and domains)."},{"line_number":283,"context_line":"Internally, both domains and projects are stored in the project table.  The"},{"line_number":284,"context_line":"uniqueness of the ID will be enforced by the database."},{"line_number":285,"context_line":""}],"source_content_type":"text/x-rst","patch_set":3,"id":"f9e58dc4_00650532","line":282,"range":{"start_line":282,"start_character":55,"end_line":282,"end_character":75},"updated":"2026-08-19 20:38:24.000000000","message":"users, projects and domains","commit_id":"1d0df9326bd7d066548dfa4d27ffa2ad67632188"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"2a77e229a2877f21dd401ea20bc1ab7706a92001","unresolved":false,"context_lines":[{"line_number":279,"context_line":"Global ID Uniqueness"},{"line_number":280,"context_line":"~~~~~~~~~~~~~~~~~~~~"},{"line_number":281,"context_line":""},{"line_number":282,"context_line":"Resource IDs must be unique across all resource types (projects and domains)."},{"line_number":283,"context_line":"Internally, both domains and projects are stored in the project table.  The"},{"line_number":284,"context_line":"uniqueness of the ID will be enforced by the database."},{"line_number":285,"context_line":""}],"source_content_type":"text/x-rst","patch_set":3,"id":"d8435303_c173a32e","line":282,"range":{"start_line":282,"start_character":55,"end_line":282,"end_character":75},"in_reply_to":"f9e58dc4_00650532","updated":"2026-08-20 10:57:55.000000000","message":"Done","commit_id":"1d0df9326bd7d066548dfa4d27ffa2ad67632188"},{"author":{"_account_id":34120,"name":"Andre Aranha","display_name":"afariasa","email":"afariasa@redhat.com","username":"afariasa"},"change_message_id":"9d904899b39d1da2da57c1a7da7104132622be9d","unresolved":true,"context_lines":[{"line_number":48,"context_line":""},{"line_number":49,"context_line":"* **Disaster-recovery re-creation.** If a project is deleted and must be"},{"line_number":50,"context_line":"  re-created (e.g. as part of a DR runbook), using the original UUID allows"},{"line_number":51,"context_line":"  previously issued tokens to reference the restored entity. Note: Role"},{"line_number":52,"context_line":"  assignments are deleted when a project is deleted, so they must be"},{"line_number":53,"context_line":"  re-created as part of the DR procedure regardless of whether the project"},{"line_number":54,"context_line":"  ID is preserved."}],"source_content_type":"text/x-rst","patch_set":4,"id":"5fae2d5c_4cbdd6c5","line":51,"updated":"2026-08-21 12:34:53.000000000","message":"Should we also soft delete the role assignments?","commit_id":"56e1660672e8b6029d80be27fc3ba34b4b8c17f2"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"ab7aa9e6a1aaf799735f6ee3e0b247ac5410da97","unresolved":true,"context_lines":[{"line_number":308,"context_line":"  Restrict explicit ID creation to administrators only. Since system"},{"line_number":309,"context_line":"  administrators already have full control over all resources, this attack"},{"line_number":310,"context_line":"  provides no additional privilege escalation."},{"line_number":311,"context_line":""},{"line_number":312,"context_line":"**Future mitigation** (follow-on work):"},{"line_number":313,"context_line":"  Implement soft delete for projects (similar to the existing soft delete"},{"line_number":314,"context_line":"  implementation for trusts). With soft delete, resources are marked"},{"line_number":315,"context_line":"  ``deleted \u003d True`` but remain in the database, preventing ID reuse. This"},{"line_number":316,"context_line":"  protection is planned for a future release and will be configurable via"},{"line_number":317,"context_line":"  ``[resource] soft_delete_projects`` option."},{"line_number":318,"context_line":""},{"line_number":319,"context_line":"See the Dependencies section for details on the soft delete implementation"},{"line_number":320,"context_line":"plan."}],"source_content_type":"text/x-rst","patch_set":4,"id":"034c368f_c890ddc6","line":317,"range":{"start_line":311,"start_character":1,"end_line":317,"end_character":45},"updated":"2026-09-04 11:37:39.000000000","message":"Is this not **essential** for this work to move forward? We don\u0027t support deployment wide deletions by default, so deleting a project in keystone does not result in deletion of e.g. nova servers belonging to that project. If we don\u0027t support soft deletion, then it\u0027s possible for someone to create a project with an explicit ID and gain control of legacy resources, right? This doesn\u0027t have to be intentional: it could happen accidentally also.\n\nConversely, if we do support soft-delete, then your disaster recovery scenario doesn\u0027t work. Attempts to recover a project will fail because the ID is \"reserved\" by the soft-deleted instance. You will need to provide a restore or undelete API for this.","commit_id":"56e1660672e8b6029d80be27fc3ba34b4b8c17f2"}]}
