)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"6c40a65a6f7fad0b3eac8223468900ca0fb7b64c","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":3,"id":"6d59d448_a3efcef5","updated":"2026-08-19 08:12:06.000000000","message":"This adds new public methods and an interactive_unscoped_auth contract to BaseAuthPlugin, and changes all federated plugins to retain their unscoped token. Please add a release note for that API and behavior; the dependent SDK change is already treating it as a public integration point.","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"d95da78f_cd124283","in_reply_to":"6d59d448_a3efcef5","updated":"2026-08-20 18:20:11.000000000","message":"Done","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"}],"keystoneauth1/identity/v3/federation.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"37fff4e0702bc43629bbdcfb2842ec515b17eaf1","unresolved":true,"context_lines":[{"line_number":33,"context_line":"    used by plugins within keystoneauth1."},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"    It also cannot go in base as it depends on token.Token for rescoping which"},{"line_number":36,"context_line":"    would create a circular dependency."},{"line_number":37,"context_line":"    \"\"\""},{"line_number":38,"context_line":""},{"line_number":39,"context_line":"    rescoping_plugin \u003d token.Token"}],"source_content_type":"text/x-python","patch_set":3,"id":"94c66ad3_b3e4ce1b","line":36,"updated":"2026-08-19 13:43:13.000000000","message":"Could we get a brief blurb here about the extension to the caching that this class adds?","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":false,"context_lines":[{"line_number":33,"context_line":"    used by plugins within keystoneauth1."},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"    It also cannot go in base as it depends on token.Token for rescoping which"},{"line_number":36,"context_line":"    would create a circular dependency."},{"line_number":37,"context_line":"    \"\"\""},{"line_number":38,"context_line":""},{"line_number":39,"context_line":"    rescoping_plugin \u003d token.Token"}],"source_content_type":"text/x-python","patch_set":3,"id":"3c4623a1_05e31b87","line":36,"in_reply_to":"94c66ad3_b3e4ce1b","updated":"2026-08-20 18:20:11.000000000","message":"Done","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"37fff4e0702bc43629bbdcfb2842ec515b17eaf1","unresolved":true,"context_lines":[{"line_number":41,"context_line":"    #: The unscoped token, kept so that a caller can take it out and put it"},{"line_number":42,"context_line":"    #: back on a later run. Not populated until an unscoped token has been"},{"line_number":43,"context_line":"    #: obtained or installed."},{"line_number":44,"context_line":"    _unscoped_auth_ref: access.AccessInfoV3 | None \u003d None"},{"line_number":45,"context_line":""},{"line_number":46,"context_line":"    def get_unscoped_cache_id_elements(self) -\u003e dict[str, str | None] | None:"},{"line_number":47,"context_line":"        \"\"\"Return what identifies this plugin\u0027s unscoped token."}],"source_content_type":"text/x-python","patch_set":3,"id":"5b307e30_69315418","line":44,"updated":"2026-08-19 13:43:13.000000000","message":"(Thinking out loud) Rather than having a single value, would it make sense to make this a map of scope to access info (with `None` indicating unscoped)? This is a private variable, which means we can change it\u0027s behaviour, but I\u0027m raising the idea now all the same.","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":true,"context_lines":[{"line_number":41,"context_line":"    #: The unscoped token, kept so that a caller can take it out and put it"},{"line_number":42,"context_line":"    #: back on a later run. Not populated until an unscoped token has been"},{"line_number":43,"context_line":"    #: obtained or installed."},{"line_number":44,"context_line":"    _unscoped_auth_ref: access.AccessInfoV3 | None \u003d None"},{"line_number":45,"context_line":""},{"line_number":46,"context_line":"    def get_unscoped_cache_id_elements(self) -\u003e dict[str, str | None] | None:"},{"line_number":47,"context_line":"        \"\"\"Return what identifies this plugin\u0027s unscoped token."}],"source_content_type":"text/x-python","patch_set":3,"id":"c8276271_e5de5edc","line":44,"in_reply_to":"5b307e30_69315418","updated":"2026-08-20 18:20:11.000000000","message":"I\u0027m not sure as we don\u0027t save any of the others for re-use.","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"37fff4e0702bc43629bbdcfb2842ec515b17eaf1","unresolved":true,"context_lines":[{"line_number":45,"context_line":""},{"line_number":46,"context_line":"    def get_unscoped_cache_id_elements(self) -\u003e dict[str, str | None] | None:"},{"line_number":47,"context_line":"        \"\"\"Return what identifies this plugin\u0027s unscoped token."},{"line_number":48,"context_line":""},{"line_number":49,"context_line":"        Returning None, as this does, means the plugin cannot describe its"},{"line_number":50,"context_line":"        unscoped token and so it must not be cached. Subclasses that can"},{"line_number":51,"context_line":"        describe it should override this."},{"line_number":52,"context_line":""},{"line_number":53,"context_line":"        The elements must not include the scope. The point of the unscoped"},{"line_number":54,"context_line":"        token is that it can be rescoped to any target, so one stored copy"},{"line_number":55,"context_line":"        serves every scope."}],"source_content_type":"text/x-python","patch_set":3,"id":"a7255815_0f7dbe1a","line":52,"range":{"start_line":48,"start_character":1,"end_line":52,"end_character":1},"updated":"2026-08-19 13:43:13.000000000","message":"This behavior differs from how `get_cache_id_elements` (in `keystoneauth1/identity/base.py:BaseIdentityPlugin`) works: that raises `NotImplementedError` while we optionally return `None`. Is there a good reason for the divergence?","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":false,"context_lines":[{"line_number":45,"context_line":""},{"line_number":46,"context_line":"    def get_unscoped_cache_id_elements(self) -\u003e dict[str, str | None] | None:"},{"line_number":47,"context_line":"        \"\"\"Return what identifies this plugin\u0027s unscoped token."},{"line_number":48,"context_line":""},{"line_number":49,"context_line":"        Returning None, as this does, means the plugin cannot describe its"},{"line_number":50,"context_line":"        unscoped token and so it must not be cached. Subclasses that can"},{"line_number":51,"context_line":"        describe it should override this."},{"line_number":52,"context_line":""},{"line_number":53,"context_line":"        The elements must not include the scope. The point of the unscoped"},{"line_number":54,"context_line":"        token is that it can be rescoped to any target, so one stored copy"},{"line_number":55,"context_line":"        serves every scope."}],"source_content_type":"text/x-python","patch_set":3,"id":"92883aaa_68c41406","line":52,"range":{"start_line":48,"start_character":1,"end_line":52,"end_character":1},"in_reply_to":"a7255815_0f7dbe1a","updated":"2026-08-20 18:20:11.000000000","message":"Done","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"37fff4e0702bc43629bbdcfb2842ec515b17eaf1","unresolved":true,"context_lines":[{"line_number":79,"context_line":"        hasher \u003d hashlib.sha256()"},{"line_number":80,"context_line":"        for key, value in sorted(elements.items()):"},{"line_number":81,"context_line":"            if value is None:"},{"line_number":82,"context_line":"                continue"},{"line_number":83,"context_line":"            # Terminate both, so that the elements cannot be read out of the"},{"line_number":84,"context_line":"            # hash in more than one way."},{"line_number":85,"context_line":"            hasher.update(key.encode(\u0027utf-8\u0027))"},{"line_number":86,"context_line":"            hasher.update(b\u0027\\x00\u0027)"},{"line_number":87,"context_line":"            hasher.update(value.encode(\u0027utf-8\u0027))"}],"source_content_type":"text/x-python","patch_set":3,"id":"bf255654_636110cd","line":84,"range":{"start_line":82,"start_character":24,"end_line":84,"end_character":40},"updated":"2026-08-19 13:43:13.000000000","message":"For another change, but is this something we want to add to the existing `get_cache_id` logic? It would cause a one-time cache invalidation but a release note should capture that","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"45b72fa96ebc3ac7f7499a846d939e5b83a42794","unresolved":false,"context_lines":[{"line_number":79,"context_line":"        hasher \u003d hashlib.sha256()"},{"line_number":80,"context_line":"        for key, value in sorted(elements.items()):"},{"line_number":81,"context_line":"            if value is None:"},{"line_number":82,"context_line":"                continue"},{"line_number":83,"context_line":"            # Terminate both, so that the elements cannot be read out of the"},{"line_number":84,"context_line":"            # hash in more than one way."},{"line_number":85,"context_line":"            hasher.update(key.encode(\u0027utf-8\u0027))"},{"line_number":86,"context_line":"            hasher.update(b\u0027\\x00\u0027)"},{"line_number":87,"context_line":"            hasher.update(value.encode(\u0027utf-8\u0027))"}],"source_content_type":"text/x-python","patch_set":3,"id":"0a6567c9_e1d0e223","line":84,"range":{"start_line":82,"start_character":24,"end_line":84,"end_character":40},"in_reply_to":"bf255654_636110cd","updated":"2026-08-20 12:01:04.000000000","message":"Already done in https://review.opendev.org/c/openstack/keystoneauth/+/1000843","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"37fff4e0702bc43629bbdcfb2842ec515b17eaf1","unresolved":true,"context_lines":[{"line_number":87,"context_line":"            hasher.update(value.encode(\u0027utf-8\u0027))"},{"line_number":88,"context_line":"            hasher.update(b\u0027\\x00\u0027)"},{"line_number":89,"context_line":""},{"line_number":90,"context_line":"        return hasher.hexdigest()"},{"line_number":91,"context_line":""},{"line_number":92,"context_line":"    def get_unscoped_auth_state(self) -\u003e str | None:"},{"line_number":93,"context_line":"        \"\"\"Retrieve the unscoped token, for a caller that wants to store it."}],"source_content_type":"text/x-python","patch_set":3,"id":"d93fc571_d63842d1","line":90,"updated":"2026-08-19 13:43:13.000000000","message":"As with `get_unscoped_cache_id_elements`, is there any reason to diverge from the `get_cache_id` implementation (particularly the base64 encoding). Mainly thinking about this from a cognitive load/future us having to grok the difference between the implementations perspective.","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":false,"context_lines":[{"line_number":87,"context_line":"            hasher.update(value.encode(\u0027utf-8\u0027))"},{"line_number":88,"context_line":"            hasher.update(b\u0027\\x00\u0027)"},{"line_number":89,"context_line":""},{"line_number":90,"context_line":"        return hasher.hexdigest()"},{"line_number":91,"context_line":""},{"line_number":92,"context_line":"    def get_unscoped_auth_state(self) -\u003e str | None:"},{"line_number":93,"context_line":"        \"\"\"Retrieve the unscoped token, for a caller that wants to store it."}],"source_content_type":"text/x-python","patch_set":3,"id":"c0db9593_63b543b3","line":90,"in_reply_to":"d93fc571_d63842d1","updated":"2026-08-20 18:20:11.000000000","message":"Done","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"6c40a65a6f7fad0b3eac8223468900ca0fb7b64c","unresolved":true,"context_lines":[{"line_number":125,"context_line":"            self._unscoped_auth_ref \u003d None"},{"line_number":126,"context_line":"            return"},{"line_number":127,"context_line":""},{"line_number":128,"context_line":"        parsed \u003d json.loads(data)"},{"line_number":129,"context_line":"        try:"},{"line_number":130,"context_line":"            auth_ref \u003d access.create("},{"line_number":131,"context_line":"                body\u003dparsed[\u0027body\u0027], auth_token\u003dparsed[\u0027auth_token\u0027]"}],"source_content_type":"text/x-python","patch_set":3,"id":"7101e207_2a473512","line":128,"updated":"2026-08-19 08:12:06.000000000","message":"set_unscoped_auth_state() documents ValueError for data not produced by this API, but valid JSON with the wrong top-level type (for example [], null, or \"x\") raises TypeError at the string-key lookup. This state is intended to come back from persistent storage, where malformed or stale data is possible. Please normalize the wrong-shape cases to ValueError and add them to test_state_that_did_not_come_from_here_is_refused.","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":false,"context_lines":[{"line_number":125,"context_line":"            self._unscoped_auth_ref \u003d None"},{"line_number":126,"context_line":"            return"},{"line_number":127,"context_line":""},{"line_number":128,"context_line":"        parsed \u003d json.loads(data)"},{"line_number":129,"context_line":"        try:"},{"line_number":130,"context_line":"            auth_ref \u003d access.create("},{"line_number":131,"context_line":"                body\u003dparsed[\u0027body\u0027], auth_token\u003dparsed[\u0027auth_token\u0027]"}],"source_content_type":"text/x-python","patch_set":3,"id":"103fb8d0_e7e34e33","line":128,"in_reply_to":"7101e207_2a473512","updated":"2026-08-20 18:20:11.000000000","message":"Done","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"}],"keystoneauth1/plugin.py":[{"author":{"_account_id":15334,"name":"Stephen Finucane","display_name":"stephenfin","email":"stephenfin@redhat.com","username":"sfinucan"},"change_message_id":"37fff4e0702bc43629bbdcfb2842ec515b17eaf1","unresolved":true,"context_lines":[{"line_number":384,"context_line":"    #: Whether authenticating requires the user to do something, such as"},{"line_number":385,"context_line":"    #: completing a login in a browser. Where it does, holding on to the"},{"line_number":386,"context_line":"    #: credential saves an interaction rather than merely a round trip."},{"line_number":387,"context_line":"    interactive_unscoped_auth \u003d False"},{"line_number":388,"context_line":""},{"line_number":389,"context_line":"    def get_unscoped_cache_id(self) -\u003e str | None:"},{"line_number":390,"context_line":"        \"\"\"Fetch an identifier for the unscoped credential of this plugin."}],"source_content_type":"text/x-python","patch_set":3,"id":"8e6bf757_ce51395b","line":387,"updated":"2026-08-19 13:43:13.000000000","message":"Could you annotate this as a ClassVar? We don\u0027t expect users to mutate this at runtime, right?\n\n```suggestion\n    interactive_unscoped_auth: ty.ClassVar[bool] \u003d False\n```","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"c93f56b43ce459a959264dfac177d24c4d91b90a","unresolved":false,"context_lines":[{"line_number":384,"context_line":"    #: Whether authenticating requires the user to do something, such as"},{"line_number":385,"context_line":"    #: completing a login in a browser. Where it does, holding on to the"},{"line_number":386,"context_line":"    #: credential saves an interaction rather than merely a round trip."},{"line_number":387,"context_line":"    interactive_unscoped_auth \u003d False"},{"line_number":388,"context_line":""},{"line_number":389,"context_line":"    def get_unscoped_cache_id(self) -\u003e str | None:"},{"line_number":390,"context_line":"        \"\"\"Fetch an identifier for the unscoped credential of this plugin."}],"source_content_type":"text/x-python","patch_set":3,"id":"0b1e64b8_b16df819","line":387,"in_reply_to":"8e6bf757_ce51395b","updated":"2026-08-20 18:20:11.000000000","message":"Done","commit_id":"1237456de5e14e330488c37036b53cf63c7d8978"}]}
