# PR#91: An Agent Marks a Matrix Message with a Reaction Version: minor > **WARNING:** This document describes only the change applied in this PR. > It may already be outdated once the next PR is merged. > Historic PR documentation is not maintained along with new PRs; treat it as a snapshot. ## Problem The user announces pull requests of hs.hsadmin.ng for review in the Matrix room "HSAdmin NG". An agent merges the approved ones and must then mark each announcement: ✅️ when the pull request is approved, ☑️ in addition when it is merged. The room is end-to-end encrypted; werkregie has a verified device of the user's account, but only reads. ## Non-Goals - No messages, replies or edits; only a reaction (`m.reaction`, `rel_type: m.annotation`). - No removal of a reaction. - No reaction to messages of other users. - No HTTP endpoint for it; the command line only. - No reaction through the running relay; see "Follow-up PRs". ## Specification ### Feature: `werkregie-dashboard matrix react` marks the newest own message that contains a text #### Scenario#91.01: an announcement gets the reaction … so that the room shows which pull request is approved. - **Given** the user's newest own message in the room "HSAdmin NG" that contains `/pulls/329`, without a reaction ✅️ of the user - **When** an agent runs `werkregie-dashboard matrix react --room "HSAdmin NG" --contains /pulls/329 ✅️` - **Then** the message has the reaction ✅️ of the user, and the command prints `REACTED` with the event IDs ##### Verified by - [MatrixReactionsTest "the target is the newest own message that contains the text"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactionsTest.kt) - [MatrixReactorTest "a message gives its text, an edit none"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt) - [MatrixReactorTest "an encrypted event gives what its cleartext shows, a decrypted one its content"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt) - [MainTest "matrix react takes room, text and key, in any order of the options"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/MainTest.kt) #### Scenario#91.02: a reaction that is there already stays single … so that an agent can repeat the command after a failure. - **Given** the message has a reaction ✅️ of the user, with or without the variation selector U+FE0F - **When** the agent runs the same command again - **Then** nothing is sent, and the command prints `PRESENT` ##### Verified by - [MatrixReactionsTest "an own reaction with the key, with or without U+FE0F, is present"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactionsTest.kt) - [MatrixReactionsTest "a reaction of another user, with another key or to another message is not present"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactionsTest.kt) - [MatrixReactorTest "a reaction gives the message it annotates and its key"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt) - [MatrixReactorTest "a reaction in the outbox counts as the user's, by its event ID once it is sent"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt) #### Scenario#91.03: no matching message … so that a wrong text does not mark another message. - **Given** no own message among the last 500 events of the room contains the text, e.g. only `/pulls/3290` for `/pulls/329` - **When** the agent runs the command - **Then** nothing is sent, and the command prints `NO-MESSAGE` and ends with exit code 1 ##### Verified by - [MatrixReactionsTest "without an own message that contains the text there is no target"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactionsTest.kt) - [MatrixReactionsTest "each outcome prints one line, and only a missing message ends with exit code 1"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactionsTest.kt) #### Scenario#91.04: no reaction while the relay syncs … so that two clients never use the device's store and keys at once. - **Given** the relay of the machine holds `sync.lock` and runs the Matrix client - **When** the agent runs the command - **Then** nothing is sent, and the command says to stop the relay first and ends with exit code 3 ##### Verified by - [MatrixReactorTest "while another process syncs, the command refuses and says how to stop the relay"](../../dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt) ## Solution - `MatrixReactions` decides, free of the client library: the room by its ID or name, the newest own message whose text contains `--contains`, not followed by a letter or digit, and whether an own reaction with the key is there already; U+FE0F does not count, since clients differ in it. - `MatrixReactor` takes `sync.lock`, opens the device's client, waits for the first sync, reads the last 500 events of the room backwards, decrypted where the device has the key, and sends `m.reaction`. Trixnity encrypts it in an encrypted room. A reaction of an earlier run that still waits in the client's outbox counts as present, so it is not sent twice. - Exit codes: 0 for `REACTED` and `PRESENT`, 1 for `NO-MESSAGE`, 2 for a wrong call, 3 for a failure, as `watch-pr`. - No token or key is printed: the command prints event IDs only, an error its message. Why the command takes `sync.lock` rather than going through the running relay: the relay holds the lock as long as it runs, and only `MatrixSource` has its client. A control socket in `MatrixSource` would make `pitestPartial` mutate all of its methods; `MatrixSource.message` alone has a Mutation-CRAP of 812, far above the limit 15 of a branch. So `MatrixSource` needs its refactoring first, in a PR of its own. Until then the agent stops the relay for the reactions and starts it again; the dashboard then misses the relay's report for that time and says so. A relay takes the lock only with its first fetch, a few seconds after its start; until then `matrix react` gets the lock, too. Checked live in the user's test room, with the relay stopped: `REACTED`, then `PRESENT` for the same call; `NO-MESSAGE` with exit code 1 for a room found by its name. A call takes about 50 s, mostly the first sync since the relay last ran. ## Release Notes - `werkregie-dashboard matrix react --room RAUM --contains TEXT EMOJI` marks the newest own message with a reaction; a running relay or local mode stops for it. ## Open Questions None. ## Follow-up PRs 1. A refactoring of `MatrixSource`: each of its methods at a Mutation-CRAP of at most 15, the limit of `pitestPartial` for the classes a branch changes. 2. Then the relay that holds `sync.lock` sends the reaction for `matrix react` through a control socket; the relay no longer stops for it.