)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"ac41a3bb_e9a588f5","updated":"2026-06-17 09:17:58.000000000","message":"Good review -- I\u0027ve accounted for these in the next patchset (and fixed another bug I found that was introduced by a bad patch split, I normally only test at the end of the chain). Please wait a little bit and I\u0027ll upload it, I need to do some internal testing in a real environment to confirm everything is working first.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":9816,"name":"Takashi Kajinami","email":"kajinamit@oss.nttdata.com","username":"kajinamit"},"change_message_id":"b99cc4456ac302910613a2aef77b7fa60b07d2cb","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":1,"id":"77e03ea0_a18f9792","updated":"2026-06-15 10:55:55.000000000","message":"Maybe most of these parts should be addressed in the parent changes, rather than this large mass fix ?","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"a08b52fa96b59857bf44d9520bcaf2025d2a4edc","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"a672349f_67dbfd59","updated":"2026-06-17 06:01:36.000000000","message":"Sorry this is 1 nit 😉","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"595b15279d3fd3b0bfe023662be3502be8d57162","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"af1abe9d_a678bcd9","updated":"2026-06-17 01:43:38.000000000","message":"Thanks Adam and Kajinami-san for your comment.  Yeah, first of all, let me carefully review this patch 😃","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"ac883c7c_df88cad7","updated":"2026-06-17 05:58:04.000000000","message":"Thanks for your patch.  After discussed with Adam, we\u0027ll proceed the following steps:\n1. I will review this patch and Adam will udpate this patchset.\n2. If it looks good, then I\u0027ll port these fixes into my HTTP driver\u0027s code with `Co-Authored-By: Adam`.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"6417311ce1c7170e1097de8e64ffce069e081787","unresolved":true,"context_lines":[],"source_content_type":"","patch_set":1,"id":"c124b906_d63c2385","in_reply_to":"77e03ea0_a18f9792","updated":"2026-06-15 15:56:31.000000000","message":"Agree, but I don\u0027t own those -- I was talking with Yushiro and posted this to show him everything because it was easier, the intention is in fact for him to absorb these changes! Good eye 😉","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"6bf595c5ef2c5e02d121e96e82cae44377584e80","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"a7139545_ce8a5b4f","updated":"2026-06-18 11:11:54.000000000","message":"I will have a few more tweaks/simplifications to this soon...","commit_id":"439084f1da55646801cc6df065edf533cc99f954"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"8349c59469bbb5fe2a398bdd06fbf5119210cf49","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":3,"id":"af504d4d_4f9b3631","updated":"2026-06-23 03:39:31.000000000","message":"Check my two new comments -- these are new to this review so you haven\u0027t seen them yet I think.","commit_id":"cd8fff240c5965bb8c1cb1ced5f44b4bea661845"}],"oslo_messaging/_drivers/http_driver/flask/app/decorators.py":[{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":true,"context_lines":[{"line_number":24,"context_line":"    def decorated_function(*args, **kwargs):"},{"line_number":25,"context_line":"        api_key \u003d request.headers.get(\u0027X-Auth\u0027)"},{"line_number":26,"context_line":"        expected_api_key \u003d current_app.config.get(\u0027api_auth_token\u0027)"},{"line_number":27,"context_line":"        if not expected_api_key:"},{"line_number":28,"context_line":"            return \"Unauthorized: auth token not configured\", 401"},{"line_number":29,"context_line":"        if not api_key:"},{"line_number":30,"context_line":"            return \"Unauthorized\", 401"}],"source_content_type":"text/x-python","patch_set":1,"id":"c2d9de39_928b9739","line":27,"updated":"2026-06-17 05:58:04.000000000","message":"When `api_auth_token` is not configured, every server-side request returns 401, but the config option is still optional. If the token is required, this should fail during service startup or be clearly documented as mandatory.\nI think `HTTPDriver.__init__`, `HTTPListenerBase.__init__`, and `Broadcaster.__init__` should raise `ConfigurationError` when `api_auth_token` is empty. That makes misconfiguration fail early instead of failing only on the first RPC request.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":24,"context_line":"    def decorated_function(*args, **kwargs):"},{"line_number":25,"context_line":"        api_key \u003d request.headers.get(\u0027X-Auth\u0027)"},{"line_number":26,"context_line":"        expected_api_key \u003d current_app.config.get(\u0027api_auth_token\u0027)"},{"line_number":27,"context_line":"        if not expected_api_key:"},{"line_number":28,"context_line":"            return \"Unauthorized: auth token not configured\", 401"},{"line_number":29,"context_line":"        if not api_key:"},{"line_number":30,"context_line":"            return \"Unauthorized\", 401"}],"source_content_type":"text/x-python","patch_set":1,"id":"6eab1169_7982b8e6","line":27,"in_reply_to":"c2d9de39_928b9739","updated":"2026-06-17 09:17:58.000000000","message":"Done. HTTPDriver.__init__ and HTTPListenerBase.__init__ now raise ConfigurationError when api_auth_token is empty (Broadcaster is covered transitively via HTTPListenerBase). Since the server already fails closed (401 on every request when the token is unset), an empty token is always a misconfiguration — so we fail fast at startup rather than on the first RPC. Added TestHTTPAuthTokenRequired covering both the listener and driver paths.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"}],"oslo_messaging/_drivers/http_driver/flask/app/factory.py":[{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":true,"context_lines":[{"line_number":28,"context_line":"    # Create the FanoutHandler once at app creation time (not per-request)"},{"line_number":29,"context_line":"    # when the broadcaster config group is available."},{"line_number":30,"context_line":"    try:"},{"line_number":31,"context_line":"        if CONF.oslo_messaging_http_broadcaster:"},{"line_number":32,"context_line":"            from oslo_messaging._drivers.http_driver.flask.app \\"},{"line_number":33,"context_line":"                import fanout_handler"},{"line_number":34,"context_line":"            app.config[\u0027fanout_handler\u0027] \u003d \\"}],"source_content_type":"text/x-python","patch_set":1,"id":"dda72c19_9c633c68","line":31,"updated":"2026-06-17 05:58:04.000000000","message":"`CONF.oslo_messaging_http_broadcaster` can raise `NoSuchOptError` when the broadcaster options are not registered. The normal HTTPDriver path only registers driver options, so this can break nova/neutron HTTP listener startup.\nI think `factory.create_app()` should not create the fanout handler implicitly. It would be safer to make this explicit, for example `create_app(service_broker\u003dNone, enable_fanout_handler\u003dFalse)`, and enable it only from the broadcaster path.\nThis is not covered by the current unit tests, so please also add a test that creates an app with only the HTTP driver options registered and verifies that listener app creation succeeds without broadcaster options.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":28,"context_line":"    # Create the FanoutHandler once at app creation time (not per-request)"},{"line_number":29,"context_line":"    # when the broadcaster config group is available."},{"line_number":30,"context_line":"    try:"},{"line_number":31,"context_line":"        if CONF.oslo_messaging_http_broadcaster:"},{"line_number":32,"context_line":"            from oslo_messaging._drivers.http_driver.flask.app \\"},{"line_number":33,"context_line":"                import fanout_handler"},{"line_number":34,"context_line":"            app.config[\u0027fanout_handler\u0027] \u003d \\"}],"source_content_type":"text/x-python","patch_set":1,"id":"c0796bf7_f195f784","line":31,"in_reply_to":"dda72c19_9c633c68","updated":"2026-06-17 09:17:58.000000000","message":"Done, using your suggested explicit approach. create_app() is now create_app(service_broker\u003dNone, enable_fanout_handler\u003dFalse) and only builds the FanoutHandler when explicitly enabled. The implicit CONF.oslo_messaging_http_broadcaster probe is gone, so the nova/neutron RPC-server path no longer touches the broadcaster opt group. Only the broadcaster passes enable_fanout_handler\u003dTrue. Added TestFlaskAppFactory (app with HTTP-driver opts only does not create the handler; handler is wired only when enabled).","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":true,"context_lines":[{"line_number":31,"context_line":"        if CONF.oslo_messaging_http_broadcaster:"},{"line_number":32,"context_line":"            from oslo_messaging._drivers.http_driver.flask.app \\"},{"line_number":33,"context_line":"                import fanout_handler"},{"line_number":34,"context_line":"            app.config[\u0027fanout_handler\u0027] \u003d \\"},{"line_number":35,"context_line":"                fanout_handler.FanoutHandler(service_broker)"},{"line_number":36,"context_line":"    except cfg.NoSuchGroupError:"},{"line_number":37,"context_line":"        LOG.debug(\"Broadcaster config group not registered; \""}],"source_content_type":"text/x-python","patch_set":1,"id":"1ed1f24d_3fa95c38","line":34,"updated":"2026-06-17 05:58:04.000000000","message":"`FanoutHandler` is now created once at app creation time and reused across requests. Since `FanoutHandler` owns a single `HTTPClient` / `requests.Session`, this change broadens session sharing across concurrent fanout requests. Please confirm this is thread-safe, or use a thread-local/per-worker session.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":31,"context_line":"        if CONF.oslo_messaging_http_broadcaster:"},{"line_number":32,"context_line":"            from oslo_messaging._drivers.http_driver.flask.app \\"},{"line_number":33,"context_line":"                import fanout_handler"},{"line_number":34,"context_line":"            app.config[\u0027fanout_handler\u0027] \u003d \\"},{"line_number":35,"context_line":"                fanout_handler.FanoutHandler(service_broker)"},{"line_number":36,"context_line":"    except cfg.NoSuchGroupError:"},{"line_number":37,"context_line":"        LOG.debug(\"Broadcaster config group not registered; \""}],"source_content_type":"text/x-python","patch_set":1,"id":"aeab0c73_1f5dcc37","line":34,"in_reply_to":"1ed1f24d_3fa95c38","updated":"2026-06-17 09:17:58.000000000","message":"Confirmed thread-safe. FanoutHandler shares a single requests.Session, which is safe for concurrent requests (the documented caveat is only about mutating session state like headers/cookies concurrently, which we don\u0027t do). Keeping it created once.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"}],"oslo_messaging/_drivers/http_driver/service_broker.py":[{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"8349c59469bbb5fe2a398bdd06fbf5119210cf49","unresolved":true,"context_lines":[{"line_number":30,"context_line":"        \"\"\"Unregister a service from the service broker.\"\"\""},{"line_number":31,"context_line":""},{"line_number":32,"context_line":"    @abc.abstractmethod"},{"line_number":33,"context_line":"    def get_services(self, service, server,"},{"line_number":34,"context_line":"                     force_revalidate\u003dFalse, fetch_all\u003dFalse):"},{"line_number":35,"context_line":"        \"\"\"Get the list of services from the service broker."},{"line_number":36,"context_line":""}],"source_content_type":"text/x-python","patch_set":3,"id":"346b00d4_81ff4898","line":33,"updated":"2026-06-23 03:39:31.000000000","message":"The params in this method were not correct in this abstract -- consul already uses force_revalidate and fetch_all in its signature, but they were missed here","commit_id":"cd8fff240c5965bb8c1cb1ced5f44b4bea661845"}],"oslo_messaging/_drivers/impl_http.py":[{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":true,"context_lines":[{"line_number":222,"context_line":"        host \u003d self.proxy_endpoint or target_service.host"},{"line_number":223,"context_line":"        url \u003d f\"{host}/{rpc_type}\""},{"line_number":224,"context_line":"        headers \u003d {"},{"line_number":225,"context_line":"            \u0027X-Auth\u0027: self.api_auth_token,"},{"line_number":226,"context_line":"            \u0027Content-Type\u0027: \u0027application/json\u0027,"},{"line_number":227,"context_line":"            \u0027X-Reverse-Proxy-Endpoint\u0027: target_service.reverse_proxy_endpoint,"},{"line_number":228,"context_line":"            \u0027X-RPCHost\u0027: target_service.rpchost,"}],"source_content_type":"text/x-python","patch_set":1,"id":"69cd7973_60ec3ad6","line":225,"updated":"2026-06-17 05:58:04.000000000","message":"When `api_auth_token\u003dNone`, the client passes `X-Auth: None` to requests, which raises `InvalidHeader` before the request even reaches the server. This is inconsistent with the server-side 401 behavior.\nI think the best fix is the same startup-time config validation above. If auth is meant to stay optional, then the client should omit the X-Auth header when unset, and the server should consistently treat unset auth as disabled.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":222,"context_line":"        host \u003d self.proxy_endpoint or target_service.host"},{"line_number":223,"context_line":"        url \u003d f\"{host}/{rpc_type}\""},{"line_number":224,"context_line":"        headers \u003d {"},{"line_number":225,"context_line":"            \u0027X-Auth\u0027: self.api_auth_token,"},{"line_number":226,"context_line":"            \u0027Content-Type\u0027: \u0027application/json\u0027,"},{"line_number":227,"context_line":"            \u0027X-Reverse-Proxy-Endpoint\u0027: target_service.reverse_proxy_endpoint,"},{"line_number":228,"context_line":"            \u0027X-RPCHost\u0027: target_service.rpchost,"}],"source_content_type":"text/x-python","patch_set":1,"id":"95d43541_f1117830","line":225,"in_reply_to":"69cd7973_60ec3ad6","updated":"2026-06-17 09:17:58.000000000","message":"Resolved by the startup validation per your comment in decorators.py — as you noted, that\u0027s the cleanest fix. With an empty token now rejected at __init__, the client can never reach the request path with api_auth_token\u003dNone, so the InvalidHeader case is unreachable.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":true,"context_lines":[{"line_number":615,"context_line":"            # segments trying shorter base topics with the remainder as"},{"line_number":616,"context_line":"            # the server name."},{"line_number":617,"context_line":"            if not service_list and target.server is None:"},{"line_number":618,"context_line":"                parts \u003d target.topic.rsplit(\u0027.\u0027, 1)"},{"line_number":619,"context_line":"                if len(parts) \u003d\u003d 2:"},{"line_number":620,"context_line":"                    base_topic, server \u003d parts"},{"line_number":621,"context_line":"                    fallback_name \u003d self._get_service_name("}],"source_content_type":"text/x-python","patch_set":1,"id":"528a75c1_66c885ad","line":618,"updated":"2026-06-17 05:58:04.000000000","message":"The commit message and comment say the code walks dot-separated topic segments, but the implementation only does a single rsplit(\u0027.\u0027, 1). This can fail when the embedded server name itself contains dots, such as `cell1.nova.conductor`.\nI think the implementation should actually try candidate splits from right to left, or the comment and commit message should be narrowed to describe the current one-level fallback. Trying all candidate splits is safer if server names may contain dots.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":615,"context_line":"            # segments trying shorter base topics with the remainder as"},{"line_number":616,"context_line":"            # the server name."},{"line_number":617,"context_line":"            if not service_list and target.server is None:"},{"line_number":618,"context_line":"                parts \u003d target.topic.rsplit(\u0027.\u0027, 1)"},{"line_number":619,"context_line":"                if len(parts) \u003d\u003d 2:"},{"line_number":620,"context_line":"                    base_topic, server \u003d parts"},{"line_number":621,"context_line":"                    fallback_name \u003d self._get_service_name("}],"source_content_type":"text/x-python","patch_set":1,"id":"9e6bfeeb_15494eda","line":618,"in_reply_to":"528a75c1_66c885ad","updated":"2026-06-17 09:17:58.000000000","message":"Oops, I had fixed this one in a later CR but the fix really belongs here. The code here now walks the dot-separated segments right-to-left (topic.split(\u0027.\u0027), trying each split point) so embedded server names containing dots (e.g. cell1.nova.conductor) resolve correctly. Comment/commit message now match the implementation.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"1e189aa4f00db7bab01623d722d70ad311f94562","unresolved":true,"context_lines":[{"line_number":674,"context_line":"    @functools.wraps(func)"},{"line_number":675,"context_line":"    def new_func(*args, **kwargs):"},{"line_number":676,"context_line":"        start_time \u003d time.time() if timeout is not None else None"},{"line_number":677,"context_line":"        for attempt in range(times + 1):"},{"line_number":678,"context_line":"            try:"},{"line_number":679,"context_line":"                # Check if we\u0027ve exceeded the messaging-layer timeout"},{"line_number":680,"context_line":"                if timeout is not None:"}],"source_content_type":"text/x-python","patch_set":1,"id":"6ab512d9_f900e4c2","line":677,"updated":"2026-06-17 05:58:04.000000000","message":"`retry\u003d-1` is passed to `range(times + 1)`, so the loop runs zero times. The RPCClient contract says `retry\u003dNone/-1` means retry forever, so the driver must at least perform the first send attempt.\nI think this should handle times is `None` or `times \u003c 0` as infinite retry. If infinite retry is intentionally unsupported by the HTTP driver, then `retry\u003d-1` should be explicitly rejected instead of silently returning None. Matching the existing `oslo.messaging` contract seems preferable.\nThis is not covered by the current unit tests, so please also add a test for `retry\u003d-1` that verifies the HTTP driver performs at least the first send attempt and does not silently return None.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":674,"context_line":"    @functools.wraps(func)"},{"line_number":675,"context_line":"    def new_func(*args, **kwargs):"},{"line_number":676,"context_line":"        start_time \u003d time.time() if timeout is not None else None"},{"line_number":677,"context_line":"        for attempt in range(times + 1):"},{"line_number":678,"context_line":"            try:"},{"line_number":679,"context_line":"                # Check if we\u0027ve exceeded the messaging-layer timeout"},{"line_number":680,"context_line":"                if timeout is not None:"}],"source_content_type":"text/x-python","patch_set":1,"id":"4839f19f_d158e072","line":677,"in_reply_to":"6ab512d9_f900e4c2","updated":"2026-06-17 09:17:58.000000000","message":"Good catch. Fixed to match the RPCClient contract. times is None or times \u003c 0 is now treated as infinite retry (itertools.count()), so at least the first attempt always runs. Added TestRetryDecorator covering retry\u003d-1 (attempts at least once / retries until success) and retry\u003d0 (exactly one attempt).","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":33451,"name":"Yushiro Furukawa","display_name":"Yushiro Furukawa","email":"yushiro.furukawa@lycorp.co.jp","username":"yushiro2"},"change_message_id":"a08b52fa96b59857bf44d9520bcaf2025d2a4edc","unresolved":true,"context_lines":[{"line_number":696,"context_line":"                # Exponential backoff with jitter: 0.5 * 2^attempt,"},{"line_number":697,"context_line":"                # capped at 30 seconds."},{"line_number":698,"context_line":"                delay \u003d min(0.5 * (2 ** attempt), 30)"},{"line_number":699,"context_line":"                delay *\u003d random.uniform(0.5, 1.0)"},{"line_number":700,"context_line":"                # Don\u0027t sleep past the remaining timeout budget."},{"line_number":701,"context_line":"                if timeout is not None:"},{"line_number":702,"context_line":"                    remaining \u003d timeout - (time.time() - start_time)"}],"source_content_type":"text/x-python","patch_set":1,"id":"d3c5863e_f6f80aaa","line":699,"updated":"2026-06-17 06:01:36.000000000","message":"nit: `random.uniform()` is used only for retry jitter, not for security-sensitive randomness, so this is functionally fine. However, `bandit` flags it as `B311` in `pep8`. Please add `# nosec B311` or switch to an approach that satisfies bandit.","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"c09d44a4225627e4d204a2782f5add756eff7a00","unresolved":true,"context_lines":[{"line_number":696,"context_line":"                # Exponential backoff with jitter: 0.5 * 2^attempt,"},{"line_number":697,"context_line":"                # capped at 30 seconds."},{"line_number":698,"context_line":"                delay \u003d min(0.5 * (2 ** attempt), 30)"},{"line_number":699,"context_line":"                delay *\u003d random.uniform(0.5, 1.0)"},{"line_number":700,"context_line":"                # Don\u0027t sleep past the remaining timeout budget."},{"line_number":701,"context_line":"                if timeout is not None:"},{"line_number":702,"context_line":"                    remaining \u003d timeout - (time.time() - start_time)"}],"source_content_type":"text/x-python","patch_set":1,"id":"f0d36693_ae7eb190","line":699,"in_reply_to":"d3c5863e_f6f80aaa","updated":"2026-06-17 09:17:58.000000000","message":"Done — # nosec B311 added on the retry-jitter call (non-crypto use).","commit_id":"c3a386396638447ae07b190ee5adad512b8c5f09"},{"author":{"_account_id":10273,"name":"Adam Harwell","email":"flux.adam@gmail.com","username":"rm_you"},"change_message_id":"8349c59469bbb5fe2a398bdd06fbf5119210cf49","unresolved":true,"context_lines":[{"line_number":662,"context_line":""},{"line_number":663,"context_line":"        LOG.debug(\"Sending message to service %s\", service_name)"},{"line_number":664,"context_line":"        if len(service_list) \u003d\u003d 0:"},{"line_number":665,"context_line":"            if target.fanout:"},{"line_number":666,"context_line":"                # The fanout relay (broadcaster) is the moral equivalent of"},{"line_number":667,"context_line":"                # the AMQP broker for fanout: it is infrastructure, not a"},{"line_number":668,"context_line":"                # target the caller chose.  Raise MessageDeliveryFailure"}],"source_content_type":"text/x-python","patch_set":3,"id":"7ce075b7_2a37dcd8","line":665,"updated":"2026-06-23 03:39:31.000000000","message":"I also just added this fix, I think it shouldn\u0027t negatively impact Consul usage?","commit_id":"cd8fff240c5965bb8c1cb1ced5f44b4bea661845"}]}
