)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"8bb05f614db9626ea15c0d4b9891444753bb53f4","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":6,"id":"1a8de862_1ff2b34c","updated":"2026-07-26 23:58:51.000000000","message":"I\u0027m not approving this as my ability to monitor deployments is spotty at the moment. But I think this looks good now that zuul is happy with it.","commit_id":"db3c2b19a76f6e1c85a3bbc6572e16ce1989dd26"}],"docker/matrix-eavesdrop/src/eavesdrop/meetings.py":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":45,"context_line":"    idx \u003d 0"},{"line_number":46,"context_line":"    while idx \u003c len(lines) and (lines[idx].startswith(\"\u003e \") or lines[idx] \u003d\u003d \"\"):"},{"line_number":47,"context_line":"        idx +\u003d 1"},{"line_number":48,"context_line":"    return \"\\n\".join(lines[idx:]).strip() or body"},{"line_number":49,"context_line":""},{"line_number":50,"context_line":""},{"line_number":51,"context_line":"class MeetingManager:"}],"source_content_type":"text/x-python","patch_set":1,"id":"08d50b54_fc9c517e","line":48,"range":{"start_line":48,"start_character":42,"end_line":48,"end_character":49},"updated":"2026-07-01 23:14:17.000000000","message":"Will this return the body with the quote lines (those beginning with `\u003e`) if every line begins with `\u003e`? `\"\\n\".join([])` returns `\u0027\u0027` which is falsey so I think it will fallback to returning all of the data if all of the data is quote data.\n\nNot sure if that is possible. But it does make me wonder if that is the intention of the `or body` statement here.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":45,"context_line":"    idx \u003d 0"},{"line_number":46,"context_line":"    while idx \u003c len(lines) and (lines[idx].startswith(\"\u003e \") or lines[idx] \u003d\u003d \"\"):"},{"line_number":47,"context_line":"        idx +\u003d 1"},{"line_number":48,"context_line":"    return \"\\n\".join(lines[idx:]).strip() or body"},{"line_number":49,"context_line":""},{"line_number":50,"context_line":""},{"line_number":51,"context_line":"class MeetingManager:"}],"source_content_type":"text/x-python","patch_set":1,"id":"11f15179_248cfa59","line":48,"range":{"start_line":48,"start_character":42,"end_line":48,"end_character":49},"in_reply_to":"08d50b54_fc9c517e","updated":"2026-07-02 05:27:31.000000000","message":"Good catch. You\u0027re right: with the old `... or body`, a reply consisting\nof nothing but quoted fallback lines would strip to \u0027\u0027 and fall back to\nreturning the quoted text, which we\u0027d then feed into the meeting. In\npractice a Matrix reply always carries non-quote content after the\nfallback, but there\u0027s no reason to pass quoted junk through. \n\nFixed in the next patchset: it now returns the stripped result (\u0027\u0027 for an all-quote\nreply), and on_message already ignores empty bodies. Added a unit test\n(test_reply_all_quote_is_empty).","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":59,"context_line":"        self.output_dir \u003d config.get(\u0027meeting_dir\u0027, \u0027/data/meetings\u0027)"},{"line_number":60,"context_line":"        self.base_url \u003d config.get(\u0027meeting_url\u0027, \u0027\u0027)"},{"line_number":61,"context_line":"        self.info_url \u003d config.get("},{"line_number":62,"context_line":"            \u0027meeting_info_url\u0027, \u0027https://opendev.org/opendev/meetbot\u0027)"},{"line_number":63,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":64,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":65,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"93136825_bbec2c25","line":62,"range":{"start_line":62,"start_character":33,"end_line":62,"end_character":68},"updated":"2026-07-01 23:14:17.000000000","message":"This should probably be updated to https://opendev.org/opendev/system-config/src/branch/master/docker/matrix-eavesdrop since the meetbot codebase is different than this codebase.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":59,"context_line":"        self.output_dir \u003d config.get(\u0027meeting_dir\u0027, \u0027/data/meetings\u0027)"},{"line_number":60,"context_line":"        self.base_url \u003d config.get(\u0027meeting_url\u0027, \u0027\u0027)"},{"line_number":61,"context_line":"        self.info_url \u003d config.get("},{"line_number":62,"context_line":"            \u0027meeting_info_url\u0027, \u0027https://opendev.org/opendev/meetbot\u0027)"},{"line_number":63,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":64,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":65,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"}],"source_content_type":"text/x-python","patch_set":1,"id":"429ccc46_cabf2b88","line":62,"range":{"start_line":62,"start_character":33,"end_line":62,"end_character":68},"in_reply_to":"93136825_bbec2c25","updated":"2026-07-02 05:27:31.000000000","message":"Agreed - updated the default to\nhttps://opendev.org/opendev/system-config/src/branch/master/docker/matrix-eavesdrop\nsince the engine now lives here. Done in next PS.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":60,"context_line":"        self.base_url \u003d config.get(\u0027meeting_url\u0027, \u0027\u0027)"},{"line_number":61,"context_line":"        self.info_url \u003d config.get("},{"line_number":62,"context_line":"            \u0027meeting_info_url\u0027, \u0027https://opendev.org/opendev/meetbot\u0027)"},{"line_number":63,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":64,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":65,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"},{"line_number":66,"context_line":"        self.state \u003d StateStore(self.state_dir, self._make_meeting)"}],"source_content_type":"text/x-python","patch_set":1,"id":"8cf933a1_516b670d","line":63,"updated":"2026-07-01 23:14:17.000000000","message":"This is not something that the irc bot version maintained right? we can use/need this because matrix is more stateful and can resume a session on restart?","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":60,"context_line":"        self.base_url \u003d config.get(\u0027meeting_url\u0027, \u0027\u0027)"},{"line_number":61,"context_line":"        self.info_url \u003d config.get("},{"line_number":62,"context_line":"            \u0027meeting_info_url\u0027, \u0027https://opendev.org/opendev/meetbot\u0027)"},{"line_number":63,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":64,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":65,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"},{"line_number":66,"context_line":"        self.state \u003d StateStore(self.state_dir, self._make_meeting)"}],"source_content_type":"text/x-python","patch_set":1,"id":"6f2d0e9d_a692b153","line":63,"in_reply_to":"8cf933a1_516b670d","updated":"2026-07-02 05:27:31.000000000","message":"Correct - the IRC bot didn\u0027t need this. The Matrix bot is a single\nlong-lived process, so a restart mid-meeting would otherwise lose the\nin-progress meeting. We journal each accepted line to disk and, on\nstartup, replay the journal into a suppressed (\"lurking\") engine instance\nso no minutes are lost and no duplicate notices are posted. Only active\nmeetings have a journal; it\u0027s removed on #endmeeting.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":113,"context_line":"        await self.client.room_send("},{"line_number":114,"context_line":"            room_id\u003droom_id,"},{"line_number":115,"context_line":"            message_type\u003d\"m.room.message\","},{"line_number":116,"context_line":"            content\u003d{\"msgtype\": \"m.notice\", \"body\": text},"},{"line_number":117,"context_line":"        )"},{"line_number":118,"context_line":""},{"line_number":119,"context_line":"    async def _set_topic(self, room_id, topic):"}],"source_content_type":"text/x-python","patch_set":1,"id":"51627151_2ee49d77","line":116,"updated":"2026-07-01 23:14:17.000000000","message":"Calling this out for other reviews (I think it is fine and probably correct). This will render the messages from the meetbot process as room notices. I think this is what the gerritbot does.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":113,"context_line":"        await self.client.room_send("},{"line_number":114,"context_line":"            room_id\u003droom_id,"},{"line_number":115,"context_line":"            message_type\u003d\"m.room.message\","},{"line_number":116,"context_line":"            content\u003d{\"msgtype\": \"m.notice\", \"body\": text},"},{"line_number":117,"context_line":"        )"},{"line_number":118,"context_line":""},{"line_number":119,"context_line":"    async def _set_topic(self, room_id, topic):"}],"source_content_type":"text/x-python","patch_set":1,"id":"f556513c_613e2402","line":116,"in_reply_to":"51627151_2ee49d77","updated":"2026-07-02 05:27:31.000000000","message":"Yes - engine output (replies/notices) is sent as m.notice, same as\ngerritbot. Clients render it as bot output and other bots ignore it, so\nit won\u0027t cause notice loops. Left as-is intentionally.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":1,"name":"James E. Blair","email":"jim@acmegating.com","username":"corvus"},"change_message_id":"9d1ffceec657dceb0699f0f5c2326f3ebcb5976f","unresolved":true,"context_lines":[{"line_number":63,"context_line":"        self.info_url \u003d config.get("},{"line_number":64,"context_line":"            \u0027meeting_info_url\u0027,"},{"line_number":65,"context_line":"            \u0027https://opendev.org/opendev/system-config/src/branch/master\u0027"},{"line_number":66,"context_line":"            \u0027/docker/matrix-eavesdrop\u0027)"},{"line_number":67,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":68,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":69,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"}],"source_content_type":"text/x-python","patch_set":6,"id":"62c03349_aeeabfc9","line":66,"updated":"2026-07-27 13:44:35.000000000","message":"Perhaps https://meetings.opendev.org/ would be a better default?","commit_id":"db3c2b19a76f6e1c85a3bbc6572e16ce1989dd26"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"67487fa5d62e2be6275b33988be61801443a0e75","unresolved":false,"context_lines":[{"line_number":63,"context_line":"        self.info_url \u003d config.get("},{"line_number":64,"context_line":"            \u0027meeting_info_url\u0027,"},{"line_number":65,"context_line":"            \u0027https://opendev.org/opendev/system-config/src/branch/master\u0027"},{"line_number":66,"context_line":"            \u0027/docker/matrix-eavesdrop\u0027)"},{"line_number":67,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":68,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":69,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"}],"source_content_type":"text/x-python","patch_set":6,"id":"d4e5a071_25441104","line":66,"in_reply_to":"505df08c_02d48250","updated":"2026-07-27 22:30:53.000000000","message":"corrected - https://review.opendev.org/c/opendev/system-config/+/998857","commit_id":"db3c2b19a76f6e1c85a3bbc6572e16ce1989dd26"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"1bf2e2b873f9249d77c1ef7b0198e8eeff6e3b8a","unresolved":false,"context_lines":[{"line_number":63,"context_line":"        self.info_url \u003d config.get("},{"line_number":64,"context_line":"            \u0027meeting_info_url\u0027,"},{"line_number":65,"context_line":"            \u0027https://opendev.org/opendev/system-config/src/branch/master\u0027"},{"line_number":66,"context_line":"            \u0027/docker/matrix-eavesdrop\u0027)"},{"line_number":67,"context_line":"        self.state_dir \u003d config.get(\u0027meeting_state_dir\u0027, \u0027/data/meeting-state\u0027)"},{"line_number":68,"context_line":"        self.start_ts_ms \u003d start_ts_ms"},{"line_number":69,"context_line":"        self.log \u003d logging.getLogger(\u0027meetings\u0027)"}],"source_content_type":"text/x-python","patch_set":6,"id":"505df08c_02d48250","line":66,"in_reply_to":"62c03349_aeeabfc9","updated":"2026-07-27 22:00:40.000000000","message":"Done - addressed in https://review.opendev.org/c/opendev/system-config/+/998855","commit_id":"db3c2b19a76f6e1c85a3bbc6572e16ce1989dd26"}],"docker/matrix-eavesdrop/src/eavesdrop/state.py":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":59,"context_line":"            handle.write("},{"line_number":60,"context_line":"                json.dumps({\"_meta\": {\"owner\": owner_nick, \"room_id\": room_id,"},{"line_number":61,"context_line":"                                      \"channel\": channel}}) + \"\\n\""},{"line_number":62,"context_line":"            )"},{"line_number":63,"context_line":"        return meeting"},{"line_number":64,"context_line":""},{"line_number":65,"context_line":"    def journal(self, room_id: str, nick: str, line: str, ts: float) -\u003e None:"}],"source_content_type":"text/x-python","patch_set":1,"id":"969efa88_bbcc1d94","line":62,"updated":"2026-07-01 23:14:17.000000000","message":"Double checking: will the existing meeting implementation handle the case where two different people attempt to start a meeting one after another? In that case whoever goes first should remain the owner.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":59,"context_line":"            handle.write("},{"line_number":60,"context_line":"                json.dumps({\"_meta\": {\"owner\": owner_nick, \"room_id\": room_id,"},{"line_number":61,"context_line":"                                      \"channel\": channel}}) + \"\\n\""},{"line_number":62,"context_line":"            )"},{"line_number":63,"context_line":"        return meeting"},{"line_number":64,"context_line":""},{"line_number":65,"context_line":"    def journal(self, room_id: str, nick: str, line: str, ts: float) -\u003e None:"}],"source_content_type":"text/x-python","patch_set":1,"id":"f11c4a33_bc4f9bb7","line":62,"in_reply_to":"969efa88_bbcc1d94","updated":"2026-07-02 05:27:31.000000000","message":"First owner wins, and it\u0027s already handled in both paths:\n\n- Live: once a meeting exists, state.get(room) is non-None, so the manager\n  never calls start() again. A second #startmeeting is just passed to the\n  engine as a line, and the engine ignores #startmeeting on an already\n  active meeting. Ownership can\u0027t be hijacked.\n- Resume: start() writes exactly one `_meta` record (open mode \"w\"), and\n  journal() only ever appends non-meta line records. So there is never a\n  second `_meta` to overwrite the owner - resume_all() always restores the\n  original owner/room/channel from that single record.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":99,"context_line":"                    if \"_meta\" in rec:"},{"line_number":100,"context_line":"                        owner \u003d rec[\"_meta\"].get(\"owner\", owner)"},{"line_number":101,"context_line":"                        room_id \u003d rec[\"_meta\"].get(\"room_id\", room_id)"},{"line_number":102,"context_line":"                        channel \u003d rec[\"_meta\"].get(\"channel\", channel)"},{"line_number":103,"context_line":"                    else:"},{"line_number":104,"context_line":"                        records.append(rec)"},{"line_number":105,"context_line":"            if not records or not room_id:"}],"source_content_type":"text/x-python","patch_set":1,"id":"e5053737_d7dd367f","line":102,"updated":"2026-07-01 23:14:17.000000000","message":"See the above question. I think in the case where we don\u0027t exit and resume if two people start a meeting that meeting will have the first owner as the owner (I think the meeting code will enforce this?). However, here we will overwrite the first owner with a second owner then create the meeting with the factory below.\n\nI think we may need to do something like\n\n```\nif owner \u003d\u003d \"unknown\":\n    owner \u003d rec[\"_meta\"].get(\"owner\")\n```\n\nThen do similar for room_id and channel? Basically the first one should win I think.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":99,"context_line":"                    if \"_meta\" in rec:"},{"line_number":100,"context_line":"                        owner \u003d rec[\"_meta\"].get(\"owner\", owner)"},{"line_number":101,"context_line":"                        room_id \u003d rec[\"_meta\"].get(\"room_id\", room_id)"},{"line_number":102,"context_line":"                        channel \u003d rec[\"_meta\"].get(\"channel\", channel)"},{"line_number":103,"context_line":"                    else:"},{"line_number":104,"context_line":"                        records.append(rec)"},{"line_number":105,"context_line":"            if not records or not room_id:"}],"source_content_type":"text/x-python","patch_set":1,"id":"de7a28e6_7ddf3a32","line":102,"in_reply_to":"e5053737_d7dd367f","updated":"2026-07-02 05:27:31.000000000","message":"The response to comment #62 answer this.\n\nSo the suggested `if owner \u003d\u003d \"unknown\": owner \u003d rec[\"_meta\"]...` guard\nisn\u0027t needed - there\u0027s only one `_meta` and it\u0027s authoritative. I\u0027ve added\ntests to lock this in: test_second_startmeeting_keeps_first_owner (live)\nand the existing resume test already asserts owner stays \"alice\" even\nafter a different user journals a line.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"}],"docker/matrix-eavesdrop/src/eavesdrop/test_identity.py":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":13,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":14,"context_line":"# limitations under the License."},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"import testtools"},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"from eavesdrop.identity import Identity"},{"line_number":19,"context_line":"from eavesdrop.identity import mxid_localpart"}],"source_content_type":"text/x-python","patch_set":1,"id":"25ec2369_0cd63a34","line":16,"range":{"start_line":16,"start_character":7,"end_line":16,"end_character":16},"updated":"2026-07-01 23:14:17.000000000","message":"I know in the past that we used testtools in order to support python2 and python3. But for new code I wonder if we can get away with unittest as long as the code base is python3 only (which this one is).","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":13,"context_line":"# See the License for the specific language governing permissions and"},{"line_number":14,"context_line":"# limitations under the License."},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"import testtools"},{"line_number":17,"context_line":""},{"line_number":18,"context_line":"from eavesdrop.identity import Identity"},{"line_number":19,"context_line":"from eavesdrop.identity import mxid_localpart"}],"source_content_type":"text/x-python","patch_set":1,"id":"9cc1d804_c1af9225","line":16,"range":{"start_line":16,"start_character":7,"end_line":16,"end_character":16},"in_reply_to":"25ec2369_0cd63a34","updated":"2026-07-02 05:27:31.000000000","message":"Agreed - this code is py3-only, so I switched all three test modules from\ntesttools to stdlib unittest (no testtools-specific APIs were used). Done\nin next PS.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"}],"docker/matrix-eavesdrop/src/eavesdrop/test_meetings.py":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":112,"context_line":"            self.room, FakeEvent(sender, body, ts_ms, **kw), channel\u003d\"testmtg\"))"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def test_ignores_own_messages(self):"},{"line_number":115,"context_line":"        self._msg(\"@logs:opendev.org\", \"#startmeeting Test\", 1000)"},{"line_number":116,"context_line":"        self.assertIsNone(self.mgr.state.get(self.room.room_id))"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_ignores_pre_start_timestamp(self):"}],"source_content_type":"text/x-python","patch_set":1,"id":"8eb82c0a_61708a0d","line":115,"range":{"start_line":115,"start_character":40,"end_line":115,"end_character":58},"updated":"2026-07-01 23:14:17.000000000","message":"It is possible I missed it but do we plumb the `test` value into the meeting? Typically this is used so that teams can operate a meeting in a dedicated meeting channel to keep that discussion out of the more asynchronous channels, but still name the meeting. For example https://meetings.opendev.org/irclogs/%23opendev-meeting/%23opendev-meeting.2026-06-30.log.html#opendev-meeting.2026-06-30.log.html#t2026-06-30T19:00:14 I ran `#startmeeting infra` which then logged the meeting to: https://meetings.opendev.org/meetings/infra/2026/infra.2026-06-30-19.00.html (note the infra name in the file paths)\n\nIt is possible I missed where this info is passed but it seems we may only send the channel/room info and not the meeting title/name suffix from the startmeeting command.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":112,"context_line":"            self.room, FakeEvent(sender, body, ts_ms, **kw), channel\u003d\"testmtg\"))"},{"line_number":113,"context_line":""},{"line_number":114,"context_line":"    def test_ignores_own_messages(self):"},{"line_number":115,"context_line":"        self._msg(\"@logs:opendev.org\", \"#startmeeting Test\", 1000)"},{"line_number":116,"context_line":"        self.assertIsNone(self.mgr.state.get(self.room.room_id))"},{"line_number":117,"context_line":""},{"line_number":118,"context_line":"    def test_ignores_pre_start_timestamp(self):"}],"source_content_type":"text/x-python","patch_set":1,"id":"04d00a13_a2bcbc80","line":115,"range":{"start_line":115,"start_character":40,"end_line":115,"end_character":58},"in_reply_to":"8eb82c0a_61708a0d","updated":"2026-07-02 05:27:31.000000000","message":"You\u0027re right, this was a real gap. The `#startmeeting \u003cname\u003e` line does reach the engine (we feed the whole message via addline, so the engine sets _meetingname), but our extraConfig didn\u0027t override filenamePattern, so the engine\u0027s default (%(channel)s) filed minutes under the room/channel instead of the meeting name.\n\nFixed to match OpenDev\u0027s IRC meetbot exactly - I lifted the pattern from\nroles/limnoria/templates/meetingLocalConfig.py.j2:\n    filenamePattern \u003d \u0027%(meetingname)s/%%Y/%(meetingname)s.%%F-%%H.%%M\u0027\nSo `#startmeeting infra` now writes meetings/infra/YYYY/infra.\u003cdate\u003e.* just like IRC, and falls back to the channel when no name is given. Strengthened test_endmeeting_writes_minutes_and_clears_state to assert the meeting name appears in every output path. Done in next PS.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"}],"docker/matrix-eavesdrop/src/eavesdrop/test_state.py":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":78,"context_line":"        self.assertIn(\"#info hi\", body)"},{"line_number":79,"context_line":""},{"line_number":80,"context_line":"    def test_end_removes_journal_and_meeting(self):"},{"line_number":81,"context_line":"        self.store.start(\"!room:od.org\", \"alice\", channel\u003d\"chan\")"},{"line_number":82,"context_line":"        self.store.end(\"!room:od.org\")"},{"line_number":83,"context_line":"        self.assertIsNone(self.store.get(\"!room:od.org\"))"},{"line_number":84,"context_line":"        self.assertFalse(os.path.exists(self.store._journal_path(\"!room:od.org\")))"}],"source_content_type":"text/x-python","patch_set":1,"id":"9b3e1bef_0c06fdfb","line":81,"updated":"2026-07-01 23:14:17.000000000","message":"We should probably assert the file exists at this point. As the case for the file not existing is trivially true if we never create it first.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":78,"context_line":"        self.assertIn(\"#info hi\", body)"},{"line_number":79,"context_line":""},{"line_number":80,"context_line":"    def test_end_removes_journal_and_meeting(self):"},{"line_number":81,"context_line":"        self.store.start(\"!room:od.org\", \"alice\", channel\u003d\"chan\")"},{"line_number":82,"context_line":"        self.store.end(\"!room:od.org\")"},{"line_number":83,"context_line":"        self.assertIsNone(self.store.get(\"!room:od.org\"))"},{"line_number":84,"context_line":"        self.assertFalse(os.path.exists(self.store._journal_path(\"!room:od.org\")))"}],"source_content_type":"text/x-python","patch_set":1,"id":"fe647a71_5c093469","line":81,"in_reply_to":"9b3e1bef_0c06fdfb","updated":"2026-07-02 05:27:31.000000000","message":"Agreed - added assertTrue(os.path.exists(...)) right after start() so the later \"file removed\" assertion isn\u0027t trivially true. Done in next PS.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":91,"context_line":"        replayed \u003d []"},{"line_number":92,"context_line":"        fresh \u003d StateStore("},{"line_number":93,"context_line":"            self.tmp,"},{"line_number":94,"context_line":"            lambda r, o, c: replayed.append(FakeMeeting(r, o, c)) or replayed[-1])"},{"line_number":95,"context_line":"        resumed \u003d fresh.resume_all()"},{"line_number":96,"context_line":"        self.assertIn(\"!room:od.org\", resumed)"},{"line_number":97,"context_line":"        meeting \u003d resumed[\"!room:od.org\"]"}],"source_content_type":"text/x-python","patch_set":1,"id":"31320575_ad6ee387","line":94,"range":{"start_line":94,"start_character":65,"end_line":94,"end_character":81},"updated":"2026-07-01 23:14:17.000000000","message":"I\u0027m not sure I understand this or case. The code should be deterministic here right? why do we need a fallback? But also `replayed \u003d []` and `replayed[-1]` should be an IndexError.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":false,"context_lines":[{"line_number":91,"context_line":"        replayed \u003d []"},{"line_number":92,"context_line":"        fresh \u003d StateStore("},{"line_number":93,"context_line":"            self.tmp,"},{"line_number":94,"context_line":"            lambda r, o, c: replayed.append(FakeMeeting(r, o, c)) or replayed[-1])"},{"line_number":95,"context_line":"        resumed \u003d fresh.resume_all()"},{"line_number":96,"context_line":"        self.assertIn(\"!room:od.org\", resumed)"},{"line_number":97,"context_line":"        meeting \u003d resumed[\"!room:od.org\"]"}],"source_content_type":"text/x-python","patch_set":1,"id":"a8d4c2ce_db33e288","line":94,"range":{"start_line":94,"start_character":65,"end_line":94,"end_character":81},"in_reply_to":"31320575_ad6ee387","updated":"2026-07-02 05:27:31.000000000","message":"Fair - that was too clever (it relied on append() returning None so the `or` falls through to replayed[-1]; not an IndexError because append runs first, but it\u0027s unreadable). Replaced with a named recording_factory() closure that appends and returns the meeting. Done in next PS.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"}],"tox.ini":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"000655981a5fbb9ec908b53d238f4d4fe2f8be8e","unresolved":true,"context_lines":[{"line_number":32,"context_line":"  # parses, but doesn\u0027t do anything."},{"line_number":33,"context_line":"  bash -c \"ANSIBLE_INVENTORY_PLUGINS\u003d./playbooks/roles/install-ansible/files/inventory_plugins ansible -i ./inventory/base/hosts.yaml not_a_host -a \u0027true\u0027\""},{"line_number":34,"context_line":"  python3 -m unittest playbooks/roles/install-ansible/files/inventory_plugins/test_yamlgroup.py"},{"line_number":35,"context_line":"  bash -c \"cd docker/matrix-eavesdrop/src \u0026\u0026 python3 -m unittest eavesdrop.test_identity eavesdrop.test_state eavesdrop.test_meetings\""},{"line_number":36,"context_line":""},{"line_number":37,"context_line":"[testenv:docs]"},{"line_number":38,"context_line":"deps \u003d -r{toxinidir}/doc/requirements.txt"}],"source_content_type":"text/x-properties","patch_set":1,"id":"af8421fc_8635dc3b","line":35,"updated":"2026-07-01 23:14:17.000000000","message":"I\u0027m not sure how others feel, but we may not want to overload the linters job for unittests. I realize you\u0027ve done that because it is an existing job that can be used to run these test cases.\n\nIt shouldn\u0027t be too hard to add a tox target for matrix eavesdrop tests then add a new zuul job that runs those jobs when the matrix eavesdrop code is updated. I\u0027m happy to point you at zuul docs and existing job configuration examples if that helps.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"18da0bbd1163a9ddfedb11e877ff046a87c20add","unresolved":false,"context_lines":[{"line_number":32,"context_line":"  # parses, but doesn\u0027t do anything."},{"line_number":33,"context_line":"  bash -c \"ANSIBLE_INVENTORY_PLUGINS\u003d./playbooks/roles/install-ansible/files/inventory_plugins ansible -i ./inventory/base/hosts.yaml not_a_host -a \u0027true\u0027\""},{"line_number":34,"context_line":"  python3 -m unittest playbooks/roles/install-ansible/files/inventory_plugins/test_yamlgroup.py"},{"line_number":35,"context_line":"  bash -c \"cd docker/matrix-eavesdrop/src \u0026\u0026 python3 -m unittest eavesdrop.test_identity eavesdrop.test_state eavesdrop.test_meetings\""},{"line_number":36,"context_line":""},{"line_number":37,"context_line":"[testenv:docs]"},{"line_number":38,"context_line":"deps \u003d -r{toxinidir}/doc/requirements.txt"}],"source_content_type":"text/x-properties","patch_set":1,"id":"728a008d_e9db084a","line":35,"in_reply_to":"6d442b75_f704bcdc","updated":"2026-07-07 05:52:14.000000000","message":"Done","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"70d413f529abf782980f95fa316bf2e2b8888faf","unresolved":true,"context_lines":[{"line_number":32,"context_line":"  # parses, but doesn\u0027t do anything."},{"line_number":33,"context_line":"  bash -c \"ANSIBLE_INVENTORY_PLUGINS\u003d./playbooks/roles/install-ansible/files/inventory_plugins ansible -i ./inventory/base/hosts.yaml not_a_host -a \u0027true\u0027\""},{"line_number":34,"context_line":"  python3 -m unittest playbooks/roles/install-ansible/files/inventory_plugins/test_yamlgroup.py"},{"line_number":35,"context_line":"  bash -c \"cd docker/matrix-eavesdrop/src \u0026\u0026 python3 -m unittest eavesdrop.test_identity eavesdrop.test_state eavesdrop.test_meetings\""},{"line_number":36,"context_line":""},{"line_number":37,"context_line":"[testenv:docs]"},{"line_number":38,"context_line":"deps \u003d -r{toxinidir}/doc/requirements.txt"}],"source_content_type":"text/x-properties","patch_set":1,"id":"e8c3aea8_ea7d6a25","line":35,"in_reply_to":"af8421fc_8635dc3b","updated":"2026-07-02 05:27:31.000000000","message":"Makes sense - I\u0027d rather set this up the way you all prefer than guess.\nI\u0027ll move the unit tests out of [testenv:linters] into a dedicated tox env\n(e.g. [testenv:matrix-eavesdrop]) and add a Zuul job scoped to\ndocker/matrix-eavesdrop/** file matchers. Could you point me at a good\nexisting example job to model it on? Happy to follow up with that as a\nseparate patch in the stack. (Leaving the linters hook in place only until\nthe dedicated job lands, unless you\u0027d prefer I split it now.)","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"},{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"aa5061561a0cc57355aa7179bd2e6b41d9a4005d","unresolved":true,"context_lines":[{"line_number":32,"context_line":"  # parses, but doesn\u0027t do anything."},{"line_number":33,"context_line":"  bash -c \"ANSIBLE_INVENTORY_PLUGINS\u003d./playbooks/roles/install-ansible/files/inventory_plugins ansible -i ./inventory/base/hosts.yaml not_a_host -a \u0027true\u0027\""},{"line_number":34,"context_line":"  python3 -m unittest playbooks/roles/install-ansible/files/inventory_plugins/test_yamlgroup.py"},{"line_number":35,"context_line":"  bash -c \"cd docker/matrix-eavesdrop/src \u0026\u0026 python3 -m unittest eavesdrop.test_identity eavesdrop.test_state eavesdrop.test_meetings\""},{"line_number":36,"context_line":""},{"line_number":37,"context_line":"[testenv:docs]"},{"line_number":38,"context_line":"deps \u003d -r{toxinidir}/doc/requirements.txt"}],"source_content_type":"text/x-properties","patch_set":1,"id":"6d442b75_f704bcdc","line":35,"in_reply_to":"e8c3aea8_ea7d6a25","updated":"2026-07-06 04:10:11.000000000","message":"`tox_envlist` is the variable that controls which tox environment to run and you can see an example of that here: https://opendev.org/zuul/zuul-jobs/src/branch/master/zuul.d/python-jobs.yaml#L225 for selecting a specific python version. In this case you\u0027d set the value to `matrix-eavesdrop` after adding that testenv to this tox.ini file.\n\nThen to control which files trigger that you can see an example of using zuul\u0027s files directive to match those here: https://opendev.org/opendev/system-config/src/branch/master/zuul.d/system-config-run.yaml#L199-L217 in this case I think you want an entry for `docker/matrix-eavesdrop/` which will match everything with that file prefix in the repo.\n\nAll of that goes into a new job that inherits from the tox jobs just like the python3.13 specific job in my first example. That can probably just go in https://opendev.org/opendev/system-config/src/branch/master/zuul.d/project.yaml since we don\u0027t have a better hoem for it at the moment. Call it tox-matrix-eavesdrop or similar and then add it to the check jobs list and the gate jobs list in project.yaml to instruct zuul to execute the job. (We have to define the job and tell zuul when to run it).\n\nAnd yes I think it would be good to add that as part of this change so that if landing the whole sequence takes time we don\u0027t end up with the weird linters job for some period of time.","commit_id":"a1987eb8b614ff721538d6da60e90f3513dea518"}],"zuul.d/project.yaml":[{"author":{"_account_id":4146,"name":"Clark Boylan","email":"cboylan@sapwetik.org","username":"cboylan"},"change_message_id":"77013755d7248bad95334dc04c30a786be810316","unresolved":true,"context_lines":[{"line_number":8,"context_line":"      bot source so it only runs when that code changes; the heavier"},{"line_number":9,"context_line":"      end-to-end meeting test runs in system-config-run-eavesdrop."},{"line_number":10,"context_line":"    vars:"},{"line_number":11,"context_line":"      tox_envlist: matrix-eavesdrop"},{"line_number":12,"context_line":"    files:"},{"line_number":13,"context_line":"      - ^docker/matrix-eavesdrop/"},{"line_number":14,"context_line":"      - ^tox.ini$"}],"source_content_type":"text/x-yaml","patch_set":3,"id":"f8c5071b_e0485073","line":11,"updated":"2026-07-07 02:07:17.000000000","message":"We also need to set `tox_install_siblings: false` to correct the error at https://zuul.opendev.org/t/openstack/build/1567f7c90abe441c9c28463517a79316/console I think this is at least partially happening because this isn\u0027t a proper python package so the siblings feature gets confused and breaks.\n\nSiblings are basically other python packages hosted within Zuul that we can do cross project testing against automatically using this feature. But this tool doesn\u0027t have any dependencies on tools hosted in zuul so we don\u0027t get any value out of the siblings feature and can simply disable it to address the issue.","commit_id":"2f4e73cdcfe9709677143b892ac989ed89c438b4"},{"author":{"_account_id":10089,"name":"Anil Shashikumar Belur","email":"abelur@linuxfoundation.org","username":"askb"},"change_message_id":"5e96d7a70489810e65cb0dcacb2f3cd0335feea7","unresolved":false,"context_lines":[{"line_number":8,"context_line":"      bot source so it only runs when that code changes; the heavier"},{"line_number":9,"context_line":"      end-to-end meeting test runs in system-config-run-eavesdrop."},{"line_number":10,"context_line":"    vars:"},{"line_number":11,"context_line":"      tox_envlist: matrix-eavesdrop"},{"line_number":12,"context_line":"    files:"},{"line_number":13,"context_line":"      - ^docker/matrix-eavesdrop/"},{"line_number":14,"context_line":"      - ^tox.ini$"}],"source_content_type":"text/x-yaml","patch_set":3,"id":"f93f93ac_34c023d7","line":11,"in_reply_to":"f8c5071b_e0485073","updated":"2026-07-07 04:15:13.000000000","message":"Done in PS4. Added `tox_install_siblings: false` to the tox-matrix-eavesdrop\njob vars, with a comment noting the env runs the vendored unittest suite\ndirectly (skip_install, no deps) so there is no installable sibling project\nto build. `tox -e matrix-eavesdrop` runs the 25 tests green.","commit_id":"2f4e73cdcfe9709677143b892ac989ed89c438b4"}]}
