Two commands for agents: matrix send writes a plain-text message into a room, matrix read prints its decrypted messages; both through the running relay or a client of their own.
See doc/prs/2026-10-23-PR#100-matrix-send-read.md.
Two commands for agents: `matrix send` writes a plain-text message into a room, `matrix read` prints its decrypted messages; both through the running relay or a client of their own.
See `doc/prs/2026-10-23-PR#100-matrix-send-read.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
ai-review/assessment AI findings; a developer approval after checking them unlocks the merge; review requested from mi; merge scheduled for when the checks succeed
ORANGE changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.81)
ORANGE 3 finding(s) that Jev rates as probably real and severe: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReading.kt:23, dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:48-50, dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/Main.kt:317-318
YELLOW tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
YELLOW change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
werkrevisor: ORANGE / FINDINGS for 254fdc93212ef11ed416a878a7ac89832b133e07.
The result comment of werkrevisor holds the details.
werkrevisor: ORANGE / FINDINGS for `254fdc93212ef11ed416a878a7ac89832b133e07`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:254fdc93212ef11ed416a878a7ac89832b133e07 -->
Only the first positional argument is consumed, and remaining arguments are never validated. An unquoted message such as Build is green therefore sends only Build and reports success instead of rejecting the wrong call. Reject leftover arguments before sending so the command exits with its usage error rather than delivering a partial message.
**[medium] Extra Arguments** (werkrevisor)
ORANGE; Jev: real 0.82, severity 2.9 of 3
Only the first positional argument is consumed, and remaining arguments are never validated. An unquoted message such as `Build is green` therefore sends only `Build` and reports success instead of rejecting the wrong call. Reject leftover arguments before sending so the command exits with its usage error rather than delivering a partial message.
Fixed in f54ad51: matrix send refuses more than one text argument with its usage, exit code 2. Test: MainTest "matrix send needs room and text, none of them blank".
Fixed in `f54ad51`: `matrix send` refuses more than one text argument with its usage, exit code 2. Test: MainTest "matrix send needs room and text, none of them blank".
The found.size == request.limit branch succeeds without confirming that afterEvent was reached. In an active room, matrix read --since $typo --limit 20 therefore returns the newest 20 messages successfully, silently skipping the interval for an incremental reader. This also contradicts the PR document's guarantee that an event ID outside the last 500 events fails; keep scanning to locate the cursor and fail if it is absent.
**[high] Stale Cursor** (werkrevisor)
ORANGE; Jev: real 0.71, severity 3.0 of 3
The `found.size == request.limit` branch succeeds without confirming that `afterEvent` was reached. In an active room, `matrix read --since $typo --limit 20` therefore returns the newest 20 messages successfully, silently skipping the interval for an incremental reader. This also contradicts the PR document's guarantee that an event ID outside the last 500 events fails; keep scanning to locate the cursor and fail if it is absent.
Fixed in f54ad51: with --since EVENT_ID the reading looks for the event beyond the limit and fails if it is not among the last 500 events; it keeps the newest limit messages after it. Test: MatrixReaderTest "an event to start after that the room does not show ends the command, also beyond the limit".
Fixed in `f54ad51`: with `--since EVENT_ID` the reading looks for the event beyond the limit and fails if it is not among the last 500 events; it keeps the newest `limit` messages after it. Test: MatrixReaderTest "an event to start after that the room does not show ends the command, also beyond the limit".
startsWith("$") is invalid Kotlin syntax because the dollar sign starts a string template. This prevents the project and the new tests from compiling. Use startsWith('$') or escape the literal dollar sign.
**[high] Build Failure** (werkrevisor)
ORANGE; Jev: real 0.91, severity 2.7 of 3
`startsWith("$")` is invalid Kotlin syntax because the dollar sign starts a string template. This prevents the project and the new tests from compiling. Use `startsWith('$')` or escape the literal dollar sign.
Not a finding: in Kotlin a $ that no identifier or { follows is a literal, so "$" compiles. The build of 254fdc9 is green, with MatrixReadingTest and MainTest using --since $e. No change.
Not a finding: in Kotlin a `$` that no identifier or `{` follows is a literal, so `"$"` compiles. The build of `254fdc9` is green, with `MatrixReadingTest` and `MainTest` using `--since $e`. No change.
claude-code marked this conversation as resolved
ai-reviewer
scheduled this pull request to auto merge when all checks succeed 2026-10-23 19:21:28 +02:00
ai-reviewer
requested review from mi 2026-10-23 19:21:28 +02:00
ai-review/assessment AI findings; a developer approval after checking them unlocks the merge; a human review request is pending; merge already scheduled
ORANGE changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.83)
ORANGE 3 finding(s) that Jev rates as probably real and severe: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-54, dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:61-64, doc/prs/2026-10-23-PR#100-matrix-send-read.md:37
YELLOW tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
YELLOW change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
hint 15 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint model recommends changes before merge
hint model rates the risk as high (advisory: Jev judged every changed file)
Findings
[high] Encrypted reaction at dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-54, ORANGE, as a line comment
[medium] Legacy Olm at dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:61-64, ORANGE, as a line comment
[medium] Event ID at doc/prs/2026-10-23-PR#100-matrix-send-read.md:37, ORANGE, as a line comment
Classifier
12 of 12 units passed all questions (1 PR document(s)).
2 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 61721, calls 1, tokens in 60119 / out 43536 (reasoning included in out), duration 599.6 s, cost 0.0702 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10452, cost 0.0004 USD, finding judge `jev-1.14.0` calls 3, tokens in 6471, cost 0.0003 USD, build file judge `jev-1.14.0` calls 16, tokens in 28413, cost 0.0012 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 0.9 s, model_review 595.6 s, finding_judge 0.4 s, config: defaults + host + repository (.werkrevisor.toml at main 367947a).
Reviewed head `f54ad51ef28e05b8b34d62bedcb3ab73247ba142` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
<!-- werkrevisor:f54ad51ef28e05b8b34d62bedcb3ab73247ba142 -->
<!-- werkrevisor-verdict:ORANGE -->
<!-- werkrevisor-blocks:bcf7660a2d227efef94b870c8d874da9,90753c6412c0ff8198b0a8ca14d88292,6194907bf7f7a67de7fc1b36797bf92f,4e95c24b86e0e9c3dd0aacbd6f7a96d3,05a2f6ef6ec9e967d5d1b2eac8c7cb45,ecee0f663c4fce1f60b0afb5b07278b0,0e9f13d59704543605a912c3283b4e41,f50c020cc567c80fc7e89a1fc8ecf277,bbc20050497d7ae157cbc5e9e8caf638,4f3d57dcc7de387fcf6ef51bca9505dd,2b71fae8113e6d5ca77ba1e6490c1f50,3c5c5970bc2c20a9a3596516487151ae,8065fcf2e8fa75a0e23bf6f8c441513b,5dd085ff1471653a9ae1af312e62696c,cc437211086f304c244b7cd843d463d6,52bf7eb7f90f4c51a1a4d3df516c0597,d8ee706b85d2b93b402edb06d79a46e5,24d75064fe90e2bcfb2647c8cd741e6c,1b1a23a5ea1e59ef43fe2e5666215384,253f89ea481a9c1d4dcc33bb4a9a0c79,e48974b700759e6876bc4554915f5703,6fb2246a143cc074aecbf6445e048f74,83d01d3fc8ff665883d8be0f100b5ca5,8886449a39476ab90246c4a80ea66496,12c8ca81c1bebbd7965bb9eb618d58a7,a7939e3b20c1d962c9bfac626eaac445,0bdd7a6b3a3a36f822323c8127047e05,c3bee07eab2d43bad35397dda3126a2f,a6bf5f93a14add230e3675e6ea38d5f5,4065b773bf1fb988087b16ff49a851ba,6bda92971b9c0c4527927db3ad3a62a0,e24d89a08e3edc6566978e6a468f8452,c19dd8cdfc8a7c0edbda6a5b7a1fa9d2,286f7cae870f46be0414504f5edf6ce9,540a7d7d5e46a636bfb77c3f48a0a295,0bc2f4ab1766daacd711d373d1f6b702,935c9b34278f3735e2fabeab72a15fd6,72bb3a0a35e5f8cde077571ac59efb23,cea550f3d1cccd347fa3b3db996fb874,0499287720e2b901062ecd215c06e24a,00c4dfdce7cac4429363974fb6fd89ec,a239074999066d29d97d815fde3c2e07,a7f7f5e758543d732fb2140841deae1a,17e69f3adb49c5b79f5746ff66cc9791,eb022560002c138ae77bd98c1f2e0be4,cdb647cd74997d341b8bf02b9dbff1fb,7912f2f388dfa08508eb5fabc30056dd,f89d0edf491d27be9b68f2c287fbf7eb,7ec03a764a28a0bc5156f87abbce4a82,b84370c3f1b8e91bb7061e8163302a2f,11495aa92fe55d4a605d0a14a1d30c2a,b2422a4248efd9e1173736a52c9c5e65,e367ba16f266660d5fc47368fa998d55,eb04647909410d239c7049b1d1eeba6f,20035056a4fb8d96fa04e25e7bc5f871,a6dcdaa660385019285fe2bac28de69d,3f8d2ab32e5c34e0cfad96a7da7d5e73,6290b9f7df6749c922f622f9d6789a7a,ac8086e5faec789cdabe63bd3a754ce8,d2bc7a09a3026c987c242889051e8755,2affc77b864cc8bf3adcf38008675d03,7ed00eda0f32c80d79f09343ef8d1c1f -->
<!-- werkrevisor-folded -->
<details><summary>🟠 werkrevisor ORANGE for <code>f54ad51</code>, superseded by <a href="http://git.javagil.de/mi/werkregie/pulls/100#issuecomment-7519">the result for <code>751fdd5</code></a></summary>
## 🟠 werkrevisor: ORANGE / FINDINGS
Change kind: `feature_with_modified_tests`.
### Reasons
- **ORANGE** changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.83)
- **ORANGE** 3 finding(s) that Jev rates as probably real and severe: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-54, dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:61-64, doc/prs/2026-10-23-PR#100-matrix-send-read.md:37
- **YELLOW** tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
- **YELLOW** change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
- **hint** 15 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** model recommends changes before merge
- **hint** model rates the risk as high (advisory: Jev judged every changed file)
### Findings
1. **[high] Encrypted reaction** at `dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-54`, ORANGE, as a line comment
2. **[medium] Legacy Olm** at `dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:61-64`, ORANGE, as a line comment
3. **[medium] Event ID** at `doc/prs/2026-10-23-PR#100-matrix-send-read.md:37`, ORANGE, as a line comment
### Classifier
12 of 12 units passed all questions (1 PR document(s)).
2 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 61721, calls 1, tokens in 60119 / out 43536 (reasoning included in out), duration 599.6 s, cost 0.0702 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10452, cost 0.0004 USD, finding judge `jev-1.14.0` calls 3, tokens in 6471, cost 0.0003 USD, build file judge `jev-1.14.0` calls 16, tokens in 28413, cost 0.0012 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 0.9 s, model_review 595.6 s, finding_judge 0.4 s, config: defaults + host + repository (.werkrevisor.toml at main 367947a).<br>
Reviewed head `f54ad51ef28e05b8b34d62bedcb3ab73247ba142` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
</sub>
</details>
werkrevisor: ORANGE / FINDINGS for f54ad51ef28e05b8b34d62bedcb3ab73247ba142.
The result comment of werkrevisor holds the details.
werkrevisor: ORANGE / FINDINGS for `f54ad51ef28e05b8b34d62bedcb3ab73247ba142`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:f54ad51ef28e05b8b34d62bedcb3ab73247ba142 -->
The limit is applied while selecting encrypted events, before entry later decrypts and discards reactions. Reactions in encrypted rooms are commonly stored as encrypted events, so the newest encrypted reaction consumes a slot and is then removed. With the default limit the command can print only 19 of 20 messages; with --limit 1, it can print nothing although the next event is a message. Decrypt and filter candidates before applying the limit, or continue scanning until the limit is filled, and add a regression test.
**[high] Encrypted reaction** (werkrevisor)
ORANGE; Jev: real 0.69, severity 2.9 of 3
The limit is applied while selecting encrypted events, before `entry` later decrypts and discards reactions. Reactions in encrypted rooms are commonly stored as encrypted events, so the newest encrypted reaction consumes a slot and is then removed. With the default limit the command can print only 19 of 20 messages; with `--limit 1`, it can print nothing although the next event is a message. Decrypt and filter candidates before applying the limit, or continue scanning until the limit is filled, and add a regression test.
Not a finding: m.relates_to stays cleartext in an encrypted event (Matrix spec, end-to-end encryption and relations), so an encrypted reaction carries its RelatesTo.Annotation outside the ciphertext, and candidate drops it before the limit. Test: MatrixReaderTest "a message or an encrypted event of any algorithm may be a message, a reaction is none", with an encrypted event that annotates. Only another encrypted non-message, e.g. a poll, can take a slot; the PR document states that. No change.
Not a finding: `m.relates_to` stays cleartext in an encrypted event (Matrix spec, end-to-end encryption and relations), so an encrypted reaction carries its `RelatesTo.Annotation` outside the ciphertext, and `candidate` drops it before the limit. Test: MatrixReaderTest "a message or an encrypted event of any algorithm may be a message, a reaction is none", with an encrypted event that annotates. Only another encrypted non-message, e.g. a poll, can take a slot; the PR document states that. No change.
candidate accepts only MegolmEncryptedMessageEventContent, so standard m.room.encrypted events using the legacy Olm algorithm never enter the candidate list and are silently omitted from matrix read. Historical rooms can contain such messages, which should at least appear as (nicht entschlüsselt) when no key is available. Accept the common encrypted-event type, including Olm, and cover it with a test.
**[medium] Legacy Olm** (werkrevisor)
ORANGE; Jev: real 0.80, severity 3.0 of 3
`candidate` accepts only `MegolmEncryptedMessageEventContent`, so standard `m.room.encrypted` events using the legacy Olm algorithm never enter the candidate list and are silently omitted from `matrix read`. Historical rooms can contain such messages, which should at least appear as `(nicht entschlüsselt)` when no key is available. Accept the common encrypted-event type, including Olm, and cover it with a test.
Olm encrypts only to-device events; Trixnity has no Olm content for room events (OlmEncryptedToDeviceEventContent only). Still, candidate now takes EncryptedMessageEventContent of any algorithm, so an unknown one shows as (nicht entschlüsselt): fixed in 751fdd5, with a test of an Unknown with algorithm m.olm.v1.curve25519-aes-sha2.
Olm encrypts only to-device events; Trixnity has no Olm content for room events (`OlmEncryptedToDeviceEventContent` only). Still, `candidate` now takes `EncryptedMessageEventContent` of any algorithm, so an unknown one shows as `(nicht entschlüsselt)`: fixed in `751fdd5`, with a test of an `Unknown` with algorithm `m.olm.v1.curve25519-aes-sha2`.
The scenario and Solution promise SENT plus the event ID, but MatrixSender.line deliberately returns only SENT when the outbox entry disappears before its ID is observed, as the new test also codifies. This race is realistic, so an agent using the returned ID as its next --since cursor can receive an incomplete result. Either guarantee the ID or amend the PR contract and CLI help to state that it is optional.
**[medium] Event ID** (werkrevisor)
ORANGE; Jev: real 0.70, severity 3.0 of 3
The scenario and Solution promise `SENT` plus the event ID, but `MatrixSender.line` deliberately returns only `SENT` when the outbox entry disappears before its ID is observed, as the new test also codifies. This race is realistic, so an agent using the returned ID as its next `--since` cursor can receive an incomplete result. Either guarantee the ID or amend the PR contract and CLI help to state that it is optional.
Fixed in 751fdd5: Scenario#100.01, the Solution and the help text say that SENT comes without the event ID when the outbox gave the message up before the client saw it; the README said so already. An agent that gets no ID reads with --since a time.
Fixed in `751fdd5`: Scenario#100.01, the Solution and the help text say that `SENT` comes without the event ID when the outbox gave the message up before the client saw it; the README said so already. An agent that gets no ID reads with `--since` a time.
ai-review/assessment AI findings; a developer approval after checking them unlocks the merge; a human review request is pending; merge already scheduled
ORANGE changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.80)
ORANGE 1 finding(s) that Jev rates as probably real and severe: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-50
YELLOW tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
YELLOW change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
hint 15 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint 2 finding(s) of low severity or rated minor by Jev: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:30, dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:29-30
hint model recommends merge with caution
Findings
[medium] Time filter stops at first older event at dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-50, ORANGE, as a line comment
[medium] Output order untested at dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:29-30, hint, as a line comment
[low] Output times use another zone at dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:30, hint, as a line comment
Classifier
12 of 12 units passed all questions (1 PR document(s)).
5 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 62206, calls 1, tokens in 60562 / out 53526 (reasoning included in out), duration 617.1 s, cost 0.0824 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10542, cost 0.0004 USD, finding judge `jev-1.14.0` calls 2, tokens in 4602, cost 0.0002 USD, build file judge `jev-1.14.0` calls 16, tokens in 28549, cost 0.0012 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 1.6 s, model_review 613.3 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 367947a).
Reviewed head `751fdd5fd29ed763c3121c1a924e529aa701cc3f` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
<!-- werkrevisor:751fdd5fd29ed763c3121c1a924e529aa701cc3f -->
<!-- werkrevisor-verdict:ORANGE -->
<!-- werkrevisor-blocks:bcf7660a2d227efef94b870c8d874da9,90753c6412c0ff8198b0a8ca14d88292,6194907bf7f7a67de7fc1b36797bf92f,3db7a25e3e4fdff9d609759b9907b47f,05a2f6ef6ec9e967d5d1b2eac8c7cb45,ecee0f663c4fce1f60b0afb5b07278b0,0e9f13d59704543605a912c3283b4e41,f50c020cc567c80fc7e89a1fc8ecf277,bbc20050497d7ae157cbc5e9e8caf638,4f3d57dcc7de387fcf6ef51bca9505dd,2b71fae8113e6d5ca77ba1e6490c1f50,3c5c5970bc2c20a9a3596516487151ae,8065fcf2e8fa75a0e23bf6f8c441513b,5dd085ff1471653a9ae1af312e62696c,cc437211086f304c244b7cd843d463d6,52bf7eb7f90f4c51a1a4d3df516c0597,d8ee706b85d2b93b402edb06d79a46e5,24d75064fe90e2bcfb2647c8cd741e6c,1b1a23a5ea1e59ef43fe2e5666215384,253f89ea481a9c1d4dcc33bb4a9a0c79,e48974b700759e6876bc4554915f5703,6fb2246a143cc074aecbf6445e048f74,83d01d3fc8ff665883d8be0f100b5ca5,8886449a39476ab90246c4a80ea66496,12c8ca81c1bebbd7965bb9eb618d58a7,a7939e3b20c1d962c9bfac626eaac445,0bdd7a6b3a3a36f822323c8127047e05,c3bee07eab2d43bad35397dda3126a2f,a6bf5f93a14add230e3675e6ea38d5f5,4065b773bf1fb988087b16ff49a851ba,6bda92971b9c0c4527927db3ad3a62a0,e24d89a08e3edc6566978e6a468f8452,c19dd8cdfc8a7c0edbda6a5b7a1fa9d2,286f7cae870f46be0414504f5edf6ce9,540a7d7d5e46a636bfb77c3f48a0a295,880444bbb207e5228eaafac253b356ea,935c9b34278f3735e2fabeab72a15fd6,72bb3a0a35e5f8cde077571ac59efb23,cea550f3d1cccd347fa3b3db996fb874,0499287720e2b901062ecd215c06e24a,00c4dfdce7cac4429363974fb6fd89ec,a239074999066d29d97d815fde3c2e07,a7f7f5e758543d732fb2140841deae1a,17e69f3adb49c5b79f5746ff66cc9791,eb022560002c138ae77bd98c1f2e0be4,cdb647cd74997d341b8bf02b9dbff1fb,7912f2f388dfa08508eb5fabc30056dd,f89d0edf491d27be9b68f2c287fbf7eb,7ec03a764a28a0bc5156f87abbce4a82,b84370c3f1b8e91bb7061e8163302a2f,11495aa92fe55d4a605d0a14a1d30c2a,b2422a4248efd9e1173736a52c9c5e65,e367ba16f266660d5fc47368fa998d55,eb04647909410d239c7049b1d1eeba6f,518d03c567bc55cafd2cb2010e8e2ce6,a6dcdaa660385019285fe2bac28de69d,3f8d2ab32e5c34e0cfad96a7da7d5e73,508f3141fa39960f5bb3bee38b5bd2ca,ac8086e5faec789cdabe63bd3a754ce8,d2bc7a09a3026c987c242889051e8755,2affc77b864cc8bf3adcf38008675d03,7ed00eda0f32c80d79f09343ef8d1c1f -->
<!-- werkrevisor-folded -->
<details><summary>🟠 werkrevisor ORANGE for <code>751fdd5</code>, superseded by <a href="http://git.javagil.de/mi/werkregie/pulls/100#issuecomment-7531">the result for <code>9ef234b</code></a></summary>
## 🟠 werkrevisor: ORANGE / FINDINGS
Change kind: `feature_with_modified_tests`.
### Reasons
- **ORANGE** changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.80)
- **ORANGE** 1 finding(s) that Jev rates as probably real and severe: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-50
- **YELLOW** tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
- **YELLOW** change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
- **hint** 15 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** 2 finding(s) of low severity or rated minor by Jev: dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:30, dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:29-30
- **hint** model recommends merge with caution
### Findings
1. **[medium] Time filter stops at first older event** at `dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-50`, ORANGE, as a line comment
2. **[medium] Output order untested** at `dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:29-30`, hint, as a line comment
3. **[low] Output times use another zone** at `dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:30`, hint, as a line comment
### Classifier
12 of 12 units passed all questions (1 PR document(s)).
5 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 62206, calls 1, tokens in 60562 / out 53526 (reasoning included in out), duration 617.1 s, cost 0.0824 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10542, cost 0.0004 USD, finding judge `jev-1.14.0` calls 2, tokens in 4602, cost 0.0002 USD, build file judge `jev-1.14.0` calls 16, tokens in 28549, cost 0.0012 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 1.6 s, model_review 613.3 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main 367947a).<br>
Reviewed head `751fdd5fd29ed763c3121c1a924e529aa701cc3f` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
</sub>
</details>
werkrevisor: ORANGE / FINDINGS for 751fdd5fd29ed763c3121c1a924e529aa701cc3f.
The result comment of werkrevisor holds the details.
werkrevisor: ORANGE / FINDINGS for `751fdd5fd29ed763c3121c1a924e529aa701cc3f`.
The result comment of werkrevisor holds the details.
<!-- werkrevisor-review:751fdd5fd29ed763c3121c1a924e529aa701cc3f -->
The oldest-first order of the matrix read output exists only in the .reversed() here; no test covers it, because MatrixReader.candidates is tested newest-first and MatrixReading.text keeps the order it is given. Removing or inverting .reversed() would keep every test green while breaking the documented order of the blocks. Extract the ordering into a testable function and assert it.
**[medium] Output order untested** (werkrevisor)
hint; Jev: real 0.60, severity 2.3 of 3
The oldest-first order of the `matrix read` output exists only in the `.reversed()` here; no test covers it, because `MatrixReader.candidates` is tested newest-first and `MatrixReading.text` keeps the order it is given. Removing or inverting `.reversed()` would keep every test green while breaking the documented order of the blocks. Extract the ordering into a testable function and assert it.
Fixed in 9ef234b: MatrixReading.text takes the messages newest first, as the room gives them, and prints them oldest first; MatrixReadingTest "each message is a block of time, sender, event ID and its indented text, the oldest first" checks the order.
Fixed in `9ef234b`: `MatrixReading.text` takes the messages newest first, as the room gives them, and prints them oldest first; MatrixReadingTest "each message is a block of time, sender, event ID and its indented text, the oldest first" checks the order.
The printed timestamps are formatted with ZoneId.systemDefault() of the process that renders the text (the relay), while --since without an offset is parsed in the zone of the CLI process. When the read is served by the relay on another machine, the agent interprets the cutoff in its own zone but sees the times in the relay's zone, so the output does not show the "local time" that README and the PR document promise. Carry the caller's zone in the request and format the times with it.
**[low] Output times use another zone** (werkrevisor)
hint
The printed timestamps are formatted with `ZoneId.systemDefault()` of the process that renders the text (the relay), while `--since` without an offset is parsed in the zone of the CLI process. When the read is served by the relay on another machine, the agent interprets the cutoff in its own zone but sees the times in the relay's zone, so the output does not show the "local time" that README and the PR document promise. Carry the caller's zone in the request and format the times with it.
Fixed in 9ef234b: ReadRequest carries the caller's zone through control.sock, and the times print in it. The socket is a local Unix socket, so the relay runs on the same machine, but its unit may have another TZ.
Fixed in `9ef234b`: `ReadRequest` carries the caller's zone through `control.sock`, and the times print in it. The socket is a local Unix socket, so the relay runs on the same machine, but its unit may have another `TZ`.
[medium] Time filter stops at first older event (werkrevisor)
ORANGE; Jev: real 0.78, severity 3.0 of 3
The scan for --since <time> walks the timeline newest first and stops at the first event whose origin_server_ts is older than the cutoff (MatrixReading.reached). Matrix orders a room by stream order, not by timestamp, so with a federated homeserver or delayed events an event with an old timestamp can precede events with newer ones. In that case the scan stops immediately and matrix read --since 2026-10-23T14:00 prints nothing although messages sent after 14:00 exist. Filter the fetched window by timestamp instead of ending the scan at the first older event.
**[medium] Time filter stops at first older event** (werkrevisor)
ORANGE; Jev: real 0.78, severity 3.0 of 3
The scan for `--since <time>` walks the timeline newest first and stops at the first event whose `origin_server_ts` is older than the cutoff (`MatrixReading.reached`). Matrix orders a room by stream order, not by timestamp, so with a federated homeserver or delayed events an event with an old timestamp can precede events with newer ones. In that case the scan stops immediately and `matrix read --since 2026-10-23T14:00` prints nothing although messages sent after 14:00 exist. Filter the fetched window by timestamp instead of ending the scan at the first older event.
Fixed in 9ef234b: an event before --since TIME is skipped, and only 20 such events in a row end the reading, so an older time stamp among newer ones no longer stops it. Test: MatrixReaderTest "an event before the time to start from is skipped, and enough of them in a row end the reading".
Fixed in `9ef234b`: an event before `--since` TIME is skipped, and only 20 such events in a row end the reading, so an older time stamp among newer ones no longer stops it. Test: MatrixReaderTest "an event before the time to start from is skipped, and enough of them in a row end the reading".
ai-review/assessment AI findings; a developer approval after checking them unlocks the merge; a human review request is pending; merge already scheduled
ORANGE changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.82)
YELLOW tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
YELLOW change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
hint 15 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
hint the model review failed: TimeoutError: review exceeded 720 seconds (advisory: Jev judged every changed file)
Classifier
12 of 12 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
Usage: model `openrouter/minimax/minimax-m3`, LLM failed (TimeoutError), tokens counted before the call 63184, duration 725.2 s, cost unknown, classifier `jev-1.14.0` calls 12, tokens in 10618, cost 0.0004 USD, build file judge `jev-1.14.0` calls 16, tokens in 29002, cost 0.0012 USD, phases: path_judge 0.8 s, classifier 0.6 s, context 0.6 s, model_review 722.7 s, config: defaults + host + repository (.werkrevisor.toml at main 367947a).
Reviewed head `9ef234b447070dc365d8844cd69b6d39912ec23e` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
<!-- werkrevisor:9ef234b447070dc365d8844cd69b6d39912ec23e -->
<!-- werkrevisor-verdict:ORANGE -->
<!-- werkrevisor-blocks:bcf7660a2d227efef94b870c8d874da9,90753c6412c0ff8198b0a8ca14d88292,6194907bf7f7a67de7fc1b36797bf92f,3db7a25e3e4fdff9d609759b9907b47f,05a2f6ef6ec9e967d5d1b2eac8c7cb45,ecee0f663c4fce1f60b0afb5b07278b0,0e9f13d59704543605a912c3283b4e41,f50c020cc567c80fc7e89a1fc8ecf277,bbc20050497d7ae157cbc5e9e8caf638,f793c23dd7139cfc8acd9f5961e20c84,f2e3bf6d799a8e5b2d9567de3ee324cb,3c5c5970bc2c20a9a3596516487151ae,8065fcf2e8fa75a0e23bf6f8c441513b,5dd085ff1471653a9ae1af312e62696c,cc437211086f304c244b7cd843d463d6,52bf7eb7f90f4c51a1a4d3df516c0597,d8ee706b85d2b93b402edb06d79a46e5,24d75064fe90e2bcfb2647c8cd741e6c,1b1a23a5ea1e59ef43fe2e5666215384,60cf017ec1bd22b2b7fb27b6f70cf262,e48974b700759e6876bc4554915f5703,0a5291caeb929265c2a3291178c40020,83d01d3fc8ff665883d8be0f100b5ca5,8886449a39476ab90246c4a80ea66496,12c8ca81c1bebbd7965bb9eb618d58a7,a7939e3b20c1d962c9bfac626eaac445,0bdd7a6b3a3a36f822323c8127047e05,c3bee07eab2d43bad35397dda3126a2f,a6bf5f93a14add230e3675e6ea38d5f5,4065b773bf1fb988087b16ff49a851ba,6bda92971b9c0c4527927db3ad3a62a0,e24d89a08e3edc6566978e6a468f8452,c19dd8cdfc8a7c0edbda6a5b7a1fa9d2,286f7cae870f46be0414504f5edf6ce9,540a7d7d5e46a636bfb77c3f48a0a295,f422a7c5001f3b8ba18e5590bd659c3f,c27fe08d1fba440534e7cb8a188bc278,72bb3a0a35e5f8cde077571ac59efb23,cea550f3d1cccd347fa3b3db996fb874,0499287720e2b901062ecd215c06e24a,00c4dfdce7cac4429363974fb6fd89ec,a239074999066d29d97d815fde3c2e07,565b57cd59db59c9bea5f3834691d6c6,17e69f3adb49c5b79f5746ff66cc9791,93b39b53790118c8c55edb2d0ddbf97f,cdb647cd74997d341b8bf02b9dbff1fb,7912f2f388dfa08508eb5fabc30056dd,f89d0edf491d27be9b68f2c287fbf7eb,7ec03a764a28a0bc5156f87abbce4a82,b84370c3f1b8e91bb7061e8163302a2f,57cb4733dcfbe199abcd4a3fdca467c7,b2422a4248efd9e1173736a52c9c5e65,e367ba16f266660d5fc47368fa998d55,eb04647909410d239c7049b1d1eeba6f,51c0b6c1bb71bb1818ec3bb584c6b97d,5cfe6f15b042cde145b96beb06944547,3f8d2ab32e5c34e0cfad96a7da7d5e73,056f36b66337a60a976e38538a2cc6b5,ac8086e5faec789cdabe63bd3a754ce8,d2bc7a09a3026c987c242889051e8755,2affc77b864cc8bf3adcf38008675d03,7ed00eda0f32c80d79f09343ef8d1c1f -->
## 🟠 werkrevisor: ORANGE / FINDINGS
Change kind: `feature_with_modified_tests`.
### Reasons
- **ORANGE** changes that weaken the checks according to Jev: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt (weakens checks 0.82)
- **YELLOW** tests lost or disabled: dashboard/src/test/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReactorTest.kt:64 removes 8 test(s) or assertion(s) without a replacement
- **YELLOW** change kind 'feature_with_modified_tests' (main code, tests, docs); AI-approvable are only: docs only, tests only added, docs and tests, refactoring with unchanged tests, feature with purely additive tests, harmless files only, mechanical changes only, mixed changes
- **hint** 15 changed file(s) that neither weaken the checks, touch security nor show a risk category of jvm-spring-backend according to Jev
- **hint** the model review failed: TimeoutError: review exceeded 720 seconds (advisory: Jev judged every changed file)
### Classifier
12 of 12 units passed all questions (1 PR document(s)).
4 answers ignored because of a confidence below [classifier] min_confidence.
<sub>
Usage: model `openrouter/minimax/minimax-m3`, LLM failed (TimeoutError), tokens counted before the call 63184, duration 725.2 s, cost unknown, classifier `jev-1.14.0` calls 12, tokens in 10618, cost 0.0004 USD, build file judge `jev-1.14.0` calls 16, tokens in 29002, cost 0.0012 USD, phases: path_judge 0.8 s, classifier 0.6 s, context 0.6 s, model_review 722.7 s, config: defaults + host + repository (.werkrevisor.toml at main 367947a).<br>
Reviewed head `9ef234b447070dc365d8844cd69b6d39912ec23e` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
</sub>
ai-review/assessment AI findings; a developer approval after checking them unlocks the merge; a human review request is pending; merge already scheduled
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two commands for agents:
matrix sendwrites a plain-text message into a room,matrix readprints its decrypted messages; both through the running relay or a client of their own.See
doc/prs/2026-10-23-PR#100-matrix-send-read.md.🤖 Generated with Claude Code
🟠 werkrevisor ORANGE for
254fdc9, superseded by the result forf54ad51🟠 werkrevisor: ORANGE / FINDINGS
Change kind:
feature_with_modified_tests.Reasons
Findings
[high] Build Failure at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReading.kt:23, ORANGE, as a line comment[high] Stale Cursor at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:48-50, ORANGE, as a line comment[medium] Extra Arguments at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/Main.kt:317-318, ORANGE, as a line commentClassifier
11 of 12 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 61592, calls 1, tokens in 59990 / out 37479 (reasoning included in out), duration 554.4 s, cost 0.0629 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10330, cost 0.0004 USD, finding judge `jev-1.14.0` calls 3, tokens in 6813, cost 0.0003 USD, build file judge `jev-1.14.0` calls 16, tokens in 28300, cost 0.0012 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 0.8 s, model_review 551.6 s, finding_judge 0.2 s, config: defaults + host + repository (.werkrevisor.toml at main3 answers ignored because of a confidence below [classifier] min_confidence.
367947a).Reviewed head `254fdc93212ef11ed416a878a7ac89832b133e07` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
werkrevisor: ORANGE / FINDINGS for
254fdc93212ef11ed416a878a7ac89832b133e07.The result comment of werkrevisor holds the details.
@@ -286,0 +314,4 @@val usage = "matrix send --room RAUM TEXT|-"fun given(value: String?) = value?.takeIf { it.isNotBlank() } ?: throw UsageError(usage)val room = given(invocation.option("--room", usage))val text = given(invocation.arguments.removeFirstOrNull())[medium] Extra Arguments (werkrevisor)
ORANGE; Jev: real 0.82, severity 2.9 of 3
Only the first positional argument is consumed, and remaining arguments are never validated. An unquoted message such as
Build is greentherefore sends onlyBuildand reports success instead of rejecting the wrong call. Reject leftover arguments before sending so the command exits with its usage error rather than delivering a partial message.Fixed in
f54ad51:matrix sendrefuses more than one text argument with its usage, exit code 2. Test: MainTest "matrix send needs room and text, none of them blank".@@ -0,0 +45,4 @@reached = MatrixReading.reached(event.event.id.full, Instant.ofEpochMilli(event.event.originTimestamp), request)if (!reached && candidate(event)) found += flow}check(reached || request.afterEvent == null || found.size == request.limit) {[high] Stale Cursor (werkrevisor)
ORANGE; Jev: real 0.71, severity 3.0 of 3
The
found.size == request.limitbranch succeeds without confirming thatafterEventwas reached. In an active room,matrix read --since $typo --limit 20therefore returns the newest 20 messages successfully, silently skipping the interval for an incremental reader. This also contradicts the PR document's guarantee that an event ID outside the last 500 events fails; keep scanning to locate the cursor and fail if it is absent.Fixed in
f54ad51: with--since EVENT_IDthe reading looks for the event beyond the limit and fails if it is not among the last 500 events; it keeps the newestlimitmessages after it. Test: MatrixReaderTest "an event to start after that the room does not show ends the command, also beyond the limit".@@ -0,0 +20,4 @@/** The reading of [room]; [since] is an event ID or a time, and with it the limit is as far as a reading goes back. */fun request(room: String, since: String?, limit: Int?, zone: ZoneId): ReadRequest {val afterEvent = since?.takeIf { it.startsWith("$") }[high] Build Failure (werkrevisor)
ORANGE; Jev: real 0.91, severity 2.7 of 3
startsWith("$")is invalid Kotlin syntax because the dollar sign starts a string template. This prevents the project and the new tests from compiling. UsestartsWith('$')or escape the literal dollar sign.Not a finding: in Kotlin a
$that no identifier or{follows is a literal, so"$"compiles. The build of254fdc9is green, withMatrixReadingTestandMainTestusing--since $e. No change.🟠 werkrevisor ORANGE for
f54ad51, superseded by the result for751fdd5🟠 werkrevisor: ORANGE / FINDINGS
Change kind:
feature_with_modified_tests.Reasons
Findings
[high] Encrypted reaction at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-54, ORANGE, as a line comment[medium] Legacy Olm at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:61-64, ORANGE, as a line comment[medium] Event ID at
doc/prs/2026-10-23-PR#100-matrix-send-read.md:37, ORANGE, as a line commentClassifier
12 of 12 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 61721, calls 1, tokens in 60119 / out 43536 (reasoning included in out), duration 599.6 s, cost 0.0702 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10452, cost 0.0004 USD, finding judge `jev-1.14.0` calls 3, tokens in 6471, cost 0.0003 USD, build file judge `jev-1.14.0` calls 16, tokens in 28413, cost 0.0012 USD, phases: path_judge 0.8 s, classifier 0.7 s, context 0.9 s, model_review 595.6 s, finding_judge 0.4 s, config: defaults + host + repository (.werkrevisor.toml at main2 answers ignored because of a confidence below [classifier] min_confidence.
367947a).Reviewed head `f54ad51ef28e05b8b34d62bedcb3ab73247ba142` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
werkrevisor: ORANGE / FINDINGS for
f54ad51ef28e05b8b34d62bedcb3ab73247ba142.The result comment of werkrevisor holds the details.
@@ -0,0 +43,4 @@internal suspend fun candidates(events: Flow<Flow<TimelineEvent>>, request: ReadRequest): List<Flow<TimelineEvent>> {val found = mutableListOf<Flow<TimelineEvent>>()var reached = falseevents.takeWhile { !reached && (request.afterEvent != null || found.size < request.limit) }.collect { flow ->[high] Encrypted reaction (werkrevisor)
ORANGE; Jev: real 0.69, severity 2.9 of 3
The limit is applied while selecting encrypted events, before
entrylater decrypts and discards reactions. Reactions in encrypted rooms are commonly stored as encrypted events, so the newest encrypted reaction consumes a slot and is then removed. With the default limit the command can print only 19 of 20 messages; with--limit 1, it can print nothing although the next event is a message. Decrypt and filter candidates before applying the limit, or continue scanning until the limit is filled, and add a regression test.Not a finding:
m.relates_tostays cleartext in an encrypted event (Matrix spec, end-to-end encryption and relations), so an encrypted reaction carries itsRelatesTo.Annotationoutside the ciphertext, andcandidatedrops it before the limit. Test: MatrixReaderTest "a message or an encrypted event of any algorithm may be a message, a reaction is none", with an encrypted event that annotates. Only another encrypted non-message, e.g. a poll, can take a slot; the PR document states that. No change.@@ -0,0 +58,4 @@withTimeoutOrNull(30.seconds) { event.first { it.content != null } } ?: event.first()/** Whether [event] may be a message: a text, a notice, a file and the like, or an encrypted event other than a reaction. */internal fun candidate(event: TimelineEvent): Boolean {[medium] Legacy Olm (werkrevisor)
ORANGE; Jev: real 0.80, severity 3.0 of 3
candidateaccepts onlyMegolmEncryptedMessageEventContent, so standardm.room.encryptedevents using the legacy Olm algorithm never enter the candidate list and are silently omitted frommatrix read. Historical rooms can contain such messages, which should at least appear as(nicht entschlüsselt)when no key is available. Accept the common encrypted-event type, including Olm, and cover it with a test.Olm encrypts only to-device events; Trixnity has no Olm content for room events (
OlmEncryptedToDeviceEventContentonly). Still,candidatenow takesEncryptedMessageEventContentof any algorithm, so an unknown one shows as(nicht entschlüsselt): fixed in751fdd5, with a test of anUnknownwith algorithmm.olm.v1.curve25519-aes-sha2.@@ -0,0 +34,4 @@- **Given** the device of `matrix login` is verified and member of the room "HSAdmin NG"- **When** an agent runs `werkregie-dashboard matrix send --room "HSAdmin NG" "Build ist grün"`- **Then** the message is in the room, encrypted like the room, and the command prints `SENT` and its event ID[medium] Event ID (werkrevisor)
ORANGE; Jev: real 0.70, severity 3.0 of 3
The scenario and Solution promise
SENTplus the event ID, butMatrixSender.linedeliberately returns onlySENTwhen the outbox entry disappears before its ID is observed, as the new test also codifies. This race is realistic, so an agent using the returned ID as its next--sincecursor can receive an incomplete result. Either guarantee the ID or amend the PR contract and CLI help to state that it is optional.Fixed in
751fdd5: Scenario#100.01, the Solution and the help text say thatSENTcomes without the event ID when the outbox gave the message up before the client saw it; the README said so already. An agent that gets no ID reads with--sincea time.🟠 werkrevisor ORANGE for
751fdd5, superseded by the result for9ef234b🟠 werkrevisor: ORANGE / FINDINGS
Change kind:
feature_with_modified_tests.Reasons
Findings
[medium] Time filter stops at first older event at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:46-50, ORANGE, as a line comment[medium] Output order untested at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:29-30, hint, as a line comment[low] Output times use another zone at
dashboard/src/main/kotlin/de/hoennig/werkregie/dashboard/sources/MatrixReader.kt:30, hint, as a line commentClassifier
12 of 12 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM done, tokens counted before the call 62206, calls 1, tokens in 60562 / out 53526 (reasoning included in out), duration 617.1 s, cost 0.0824 USD (reported), classifier `jev-1.14.0` calls 12, tokens in 10542, cost 0.0004 USD, finding judge `jev-1.14.0` calls 2, tokens in 4602, cost 0.0002 USD, build file judge `jev-1.14.0` calls 16, tokens in 28549, cost 0.0012 USD, phases: path_judge 0.7 s, classifier 0.5 s, context 1.6 s, model_review 613.3 s, finding_judge 0.3 s, config: defaults + host + repository (.werkrevisor.toml at main5 answers ignored because of a confidence below [classifier] min_confidence.
367947a).Reviewed head `751fdd5fd29ed763c3121c1a924e529aa701cc3f` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
werkrevisor: ORANGE / FINDINGS for
751fdd5fd29ed763c3121c1a924e529aa701cc3f.The result comment of werkrevisor holds the details.
@@ -0,0 +26,4 @@object MatrixReader {/** Reads with [client], which has synced once since it started; the text the command prints. */internal suspend fun read(client: MatrixClient, roomId: RoomId, request: ReadRequest): String = withTimeout(5.minutes) {val events = coroutineScope { candidates(client, roomId, request).map { async { decrypted(it) } }.awaitAll() }[medium] Output order untested (werkrevisor)
hint; Jev: real 0.60, severity 2.3 of 3
The oldest-first order of the
matrix readoutput exists only in the.reversed()here; no test covers it, becauseMatrixReader.candidatesis tested newest-first andMatrixReading.textkeeps the order it is given. Removing or inverting.reversed()would keep every test green while breaking the documented order of the blocks. Extract the ordering into a testable function and assert it.Fixed in
9ef234b:MatrixReading.texttakes the messages newest first, as the room gives them, and prints them oldest first; MatrixReadingTest "each message is a block of time, sender, event ID and its indented text, the oldest first" checks the order.@@ -0,0 +27,4 @@/** Reads with [client], which has synced once since it started; the text the command prints. */internal suspend fun read(client: MatrixClient, roomId: RoomId, request: ReadRequest): String = withTimeout(5.minutes) {val events = coroutineScope { candidates(client, roomId, request).map { async { decrypted(it) } }.awaitAll() }MatrixReading.text(events.mapNotNull(::entry).reversed(), ZoneId.systemDefault())[low] Output times use another zone (werkrevisor)
hint
The printed timestamps are formatted with
ZoneId.systemDefault()of the process that renders the text (the relay), while--sincewithout an offset is parsed in the zone of the CLI process. When the read is served by the relay on another machine, the agent interprets the cutoff in its own zone but sees the times in the relay's zone, so the output does not show the "local time" that README and the PR document promise. Carry the caller's zone in the request and format the times with it.Fixed in
9ef234b:ReadRequestcarries the caller's zone throughcontrol.sock, and the times print in it. The socket is a local Unix socket, so the relay runs on the same machine, but its unit may have anotherTZ.@@ -0,0 +43,4 @@internal suspend fun candidates(events: Flow<Flow<TimelineEvent>>, request: ReadRequest): List<Flow<TimelineEvent>> {val found = mutableListOf<Flow<TimelineEvent>>()var reached = falseevents.takeWhile { !reached && (request.afterEvent != null || found.size < request.limit) }.collect { flow ->[medium] Time filter stops at first older event (werkrevisor)
ORANGE; Jev: real 0.78, severity 3.0 of 3
The scan for
--since <time>walks the timeline newest first and stops at the first event whoseorigin_server_tsis older than the cutoff (MatrixReading.reached). Matrix orders a room by stream order, not by timestamp, so with a federated homeserver or delayed events an event with an old timestamp can precede events with newer ones. In that case the scan stops immediately andmatrix read --since 2026-10-23T14:00prints nothing although messages sent after 14:00 exist. Filter the fetched window by timestamp instead of ending the scan at the first older event.Fixed in
9ef234b: an event before--sinceTIME is skipped, and only 20 such events in a row end the reading, so an older time stamp among newer ones no longer stops it. Test: MatrixReaderTest "an event before the time to start from is skipped, and enough of them in a row end the reading".🟠 werkrevisor: ORANGE / FINDINGS
Change kind:
feature_with_modified_tests.Reasons
Classifier
12 of 12 units passed all questions (1 PR document(s)).
Usage: model `openrouter/minimax/minimax-m3`, LLM failed (TimeoutError), tokens counted before the call 63184, duration 725.2 s, cost unknown, classifier `jev-1.14.0` calls 12, tokens in 10618, cost 0.0004 USD, build file judge `jev-1.14.0` calls 16, tokens in 29002, cost 0.0012 USD, phases: path_judge 0.8 s, classifier 0.6 s, context 0.6 s, model_review 722.7 s, config: defaults + host + repository (.werkrevisor.toml at main4 answers ignored because of a confidence below [classifier] min_confidence.
367947a).Reviewed head `9ef234b447070dc365d8844cd69b6d39912ec23e` against base `367947a4816effc84a1428bc387976cc64995367`, werkrevisor 0.65.2.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.