fix(operator): stop EVM writer RPC/cursor livelock (#138) #160

Merged
PlasticDigits merged 2 commits from fix/gl-138-evm-writer-livelock into main 2026-08-31 12:11:22 +00:00
PlasticDigits commented 2026-08-31 06:02:51 +00:00 (Migrated from gitlab.com)

Summary

  • Method-level eth_getLogs fallback shared by the EVM watcher and writer, so an endpoint that answers eth_blockNumber but rate-limits logs no longer livelocks the cursor at block 0.
  • Sticky contiguous event cursor with jittered per-chain backoff; isolated writer loops so one degraded chain cannot stall Terra or other EVM writers or hold shutdown.
  • Bounded negative source-verification retry (size, TTL, per-cycle cap) so stale or attacker-created pending withdrawals cannot amplify RPC work. Fail-closed approval and contract enumeration are unchanged.

Closes #138.

Invariants: docs/OPERATOR_WRITER_INVARIANTS.md (INV-OP-W1–W10). Agent notes: skills/agent-operator-evm-writer-rpc.md.

Test plan

  • Unit: sticky first-poll range, partial-chunk cursor, recovery scans failed range first (poll_cursor)
  • Unit: negative retry suppress / TTL / terminal eviction / size bound
  • Unit: config rejects 0 / overflow / out-of-range
  • Mock JSON-RPC: primary rate-limits eth_getLogs, fallback succeeds; all-down errors; logs path does not call eth_blockNumber
  • cargo test --bins in packages/operator (83 passed) and multichain-rs rpc_fallback tests
  • cargo clippy -- -D warnings for operator (Makefile equivalent)
  • Operator restart against Anvil: first-poll lookback once, no repeated first-poll info logs
  • Fallback RPC: primary eth_blockNumber ok / eth_getLogs 429, second URL serves logs; cursor reaches head
  • Regression E2E: Terra→EVM, EVM→EVM, Solana→EVM enumeration + event discovery when infra is up
## Summary - Method-level `eth_getLogs` fallback shared by the EVM watcher and writer, so an endpoint that answers `eth_blockNumber` but rate-limits logs no longer livelocks the cursor at block 0. - Sticky contiguous event cursor with jittered per-chain backoff; isolated writer loops so one degraded chain cannot stall Terra or other EVM writers or hold shutdown. - Bounded negative source-verification retry (size, TTL, per-cycle cap) so stale or attacker-created pending withdrawals cannot amplify RPC work. Fail-closed approval and contract enumeration are unchanged. Closes #138. Invariants: [`docs/OPERATOR_WRITER_INVARIANTS.md`](docs/OPERATOR_WRITER_INVARIANTS.md) (INV-OP-W1–W10). Agent notes: [`skills/agent-operator-evm-writer-rpc.md`](skills/agent-operator-evm-writer-rpc.md). ## Test plan - [x] Unit: sticky first-poll range, partial-chunk cursor, recovery scans failed range first (`poll_cursor`) - [x] Unit: negative retry suppress / TTL / terminal eviction / size bound - [x] Unit: config rejects 0 / overflow / out-of-range - [x] Mock JSON-RPC: primary rate-limits `eth_getLogs`, fallback succeeds; all-down errors; logs path does not call `eth_blockNumber` - [x] `cargo test --bins` in `packages/operator` (83 passed) and `multichain-rs` rpc_fallback tests - [x] `cargo clippy -- -D warnings` for operator (Makefile equivalent) - [ ] Operator restart against Anvil: first-poll lookback once, no repeated first-poll info logs - [ ] Fallback RPC: primary `eth_blockNumber` ok / `eth_getLogs` 429, second URL serves logs; cursor reaches head - [ ] Regression E2E: Terra→EVM, EVM→EVM, Solana→EVM enumeration + event discovery when infra is up
PlasticDigits commented 2026-08-31 09:14:53 +00:00 (Migrated from gitlab.com)

RECOMMEND: FIX

Reviewed against #138. The livelock fix is the right design: method-level eth_getLogs fallback, sticky contiguous cursor, jittered backoff, isolated writer loops, bounded negative verification retry. Fail-closed approval and contract enumeration remain. This is not a BLOCK (no event-range skip, no weakened source verification, no prompt injection).

Security

Vector Result
Approve without source verify Unchanged fail-closed verify_deposit_on_source
Cursor skip of unobserved logs MET — EventPollCursor advances only on chunk success; sticky first-poll from
Primary eth_blockNumber OK / eth_getLogs 429 MET — shared get_logs_with_endpoint_fallback; mock JSON-RPC tests
Attacker unapproved spam Bounded cache + TTL + WRITER_MAX_VERIFY_PER_CYCLE. Backoff rotates so the tail of the enumerable set is not permanently starved once the head is suppressed
Malicious/wrong-chain fallback INV-OP-W1: chain-ID verification at startup not removed. Fallback uses the same validated URL list
Reorg / Anvil rewind detect_reset + cursor reset; enumeration remains durable discovery
Late deposit liveness TTL expiry + invalidate_for_immediate_retry on new WithdrawSubmit
Hash churn / unbounded memory negative_retry_cache_size eviction
RPC secret in logs PARTIAL — see below
Config 0 / overflow MET — poll_config rejects out of range
Oracle manipulation N/A beyond “don’t treat a random RPC as oracle”; endpoints are configured + chain-ID checked
DB leak No new secret-bearing metric labels (evm-<chainId> + method)
One degraded chain stalls others / shutdown MET — tokio::spawn per writer; 5s shutdown wait

Medium: path API keys survive sanitization

sanitize_rpc_endpoint strips userinfo and query (?apiKey=) but keeps the path. Common providers put credentials in the path:

  • Alchemy https://….alchemy.com/v2/<API_KEY>
  • Infura https://….infura.io/v3/<PROJECT_ID>

INV-OP-W9 says credentials never appear. Redact the last path segment when it looks like a key, or log scheme://host[:port] only.

Enumeration in enumerate_and_approve still logs rpc = %url (raw). Writer poll logs were moved to the sanitized helper; this leftover should use sanitize_rpc_endpoint too.

#138 acceptance criteria

Criterion Status
Fallback eth_getLogs then advance cursor MET
All endpoints fail → cursor stays; jittered backoff MET
Recovery scans failed range first MET (poll_cursor tests)
Partial chunk success does not skip MET
No unbounded first-poll loop MET (sticky from)
Degraded chain does not block others / shutdown MET
Negative retry + later-valid deposit MET (TTL / invalidate)
Cache bounded; terminal evicted MET
Enumeration still fail-closed MET
Production logs summary-oriented MET (default RUST_LOG=info; first-poll log once)
Metrics: cursor, fallback, suppressed, cache MET
Config documented and validated MET

Testing

Strong unit coverage: sticky lookback, partial chunks, recovery order, negative suppress/TTL/size, config bounds, mock RPC primary-429/fallback-OK.

Unchecked vs issue verification (do before calling this done in production):

  • Operator restart against Anvil: lookback once, no repeated first-poll info logs.
  • Fallback RPC soak: primary blockNumber OK / getLogs 429, second URL serves logs, cursor reaches head.
  • Regression E2E: Terra→EVM, EVM→EVM, Solana→EVM enumeration + event discovery.

Gaps

  • Unapproved spam is bounded per operator cycle, not on-chain. Combined with !158, attacker-created unapproved Terra/EVM withdrawals can still grow the active/pending set; this MR only stops retry amplification. That split matches the issues.
  • Default writer interval is now configurable; confirm production env examples match the new bounds.

Fix the sanitizer (and enumeration log), run the Anvil/fallback checks, then this is an ACCEPT.

RECOMMEND: FIX Reviewed against #138. The livelock fix is the right design: method-level `eth_getLogs` fallback, sticky contiguous cursor, jittered backoff, isolated writer loops, bounded negative verification retry. Fail-closed approval and contract enumeration remain. This is **not** a BLOCK (no event-range skip, no weakened source verification, no prompt injection). ## Security | Vector | Result | |---|---| | Approve without source verify | Unchanged fail-closed `verify_deposit_on_source` | | Cursor skip of unobserved logs | MET — `EventPollCursor` advances only on chunk success; sticky first-poll `from` | | Primary `eth_blockNumber` OK / `eth_getLogs` 429 | MET — shared `get_logs_with_endpoint_fallback`; mock JSON-RPC tests | | Attacker unapproved spam | Bounded cache + TTL + `WRITER_MAX_VERIFY_PER_CYCLE`. Backoff rotates so the tail of the enumerable set is not permanently starved once the head is suppressed | | Malicious/wrong-chain fallback | INV-OP-W1: chain-ID verification at startup not removed. Fallback uses the same validated URL list | | Reorg / Anvil rewind | `detect_reset` + cursor reset; enumeration remains durable discovery | | Late deposit liveness | TTL expiry + `invalidate_for_immediate_retry` on new `WithdrawSubmit` | | Hash churn / unbounded memory | `negative_retry_cache_size` eviction | | **RPC secret in logs** | **PARTIAL** — see below | | Config 0 / overflow | MET — `poll_config` rejects out of range | | Oracle manipulation | N/A beyond “don’t treat a random RPC as oracle”; endpoints are configured + chain-ID checked | | DB leak | No new secret-bearing metric labels (`evm-<chainId>` + method) | | One degraded chain stalls others / shutdown | MET — `tokio::spawn` per writer; 5s shutdown wait | ### Medium: path API keys survive sanitization `sanitize_rpc_endpoint` strips userinfo and query (`?apiKey=`) but **keeps the path**. Common providers put credentials in the path: - Alchemy `https://….alchemy.com/v2/<API_KEY>` - Infura `https://….infura.io/v3/<PROJECT_ID>` INV-OP-W9 says credentials never appear. Redact the last path segment when it looks like a key, or log `scheme://host[:port]` only. Enumeration in `enumerate_and_approve` still logs `rpc = %url` (raw). Writer poll logs were moved to the sanitized helper; this leftover should use `sanitize_rpc_endpoint` too. ## #138 acceptance criteria | Criterion | Status | |---|---| | Fallback `eth_getLogs` then advance cursor | MET | | All endpoints fail → cursor stays; jittered backoff | MET | | Recovery scans failed range first | MET (`poll_cursor` tests) | | Partial chunk success does not skip | MET | | No unbounded first-poll loop | MET (sticky `from`) | | Degraded chain does not block others / shutdown | MET | | Negative retry + later-valid deposit | MET (TTL / invalidate) | | Cache bounded; terminal evicted | MET | | Enumeration still fail-closed | MET | | Production logs summary-oriented | MET (default `RUST_LOG=info`; first-poll log once) | | Metrics: cursor, fallback, suppressed, cache | MET | | Config documented and validated | MET | ## Testing Strong unit coverage: sticky lookback, partial chunks, recovery order, negative suppress/TTL/size, config bounds, mock RPC primary-429/fallback-OK. Unchecked vs issue verification (do before calling this done in production): - Operator restart against Anvil: lookback once, no repeated first-poll info logs. - Fallback RPC soak: primary blockNumber OK / getLogs 429, second URL serves logs, cursor reaches head. - Regression E2E: Terra→EVM, EVM→EVM, Solana→EVM enumeration + event discovery. ## Gaps - Unapproved spam is bounded **per operator cycle**, not on-chain. Combined with !158, attacker-created unapproved Terra/EVM withdrawals can still grow the active/pending set; this MR only stops retry amplification. That split matches the issues. - Default writer interval is now configurable; confirm production env examples match the new bounds. Fix the sanitizer (and enumeration log), run the Anvil/fallback checks, then this is an ACCEPT.
PlasticDigits commented 2026-08-31 09:17:02 +00:00 (Migrated from gitlab.com)

Follow-up (second pass): still RECOMMEND: FIX. INV-OP-W9 is still not closed; additional items:

  1. High — error = %e still embeds RPC URLs. packages/operator/src/rpc_fallback.rs sanitizes the rpc field but logs error = %e. Hyper/reqwest Display commonly includes https://host/v2/<key>. The all-endpoints eyre! interpolates that last error too. Strip URLs from error Display (or log a classified code only).

  2. High — more raw rpc = %url sites in the same writer. Besides enumeration: cancel-window warn and verify_evm_deposit_on_chain info/warn (writers/evm.rs ~184, 934, 975, 997, 1006). Watcher init primary_rpc = %rpc_urls[0] is pre-existing but the same invariant.

  3. Medium — jitter is deterministic (INV-OP-W7). Seeds are chain_id ^ chunk_start / hash bytes (writers/evm.rs). Co-scheduled operators retry in lockstep. Mix in process-start entropy. Consensus-head failure seed chain_id ^ Instant::now().elapsed() on a just-created Instant is ~0.

  4. Medium — event-poll verifies ignore WRITER_MAX_VERIFY_PER_CYCLE. Enumeration is capped; the event loop verifies every WithdrawSubmit in the chunk and invalidates negative-retry. A gas-funded burst in one chunk is uncapped for that cycle. Count event-path verifies toward the cap (or a sibling cap).

  5. Medium — full-cache eviction picks earliest next_retry, which re-queues those hashes as unknown next cycle. Prefer inserted_at FIFO under size pressure.

  6. Should-fix: empty/wrong-chain fallback eth_getLogs success advances the event cursor with no runtime eth_chainId check. Enumeration is the backstop; do not treat empty fallback logs as observed without a verified endpoint.

Fail-closed verify_deposit_on_source is unchanged. Failed log ranges are still not skipped. A writer-level mock livelock test (primary blockNumber OK, logs 429, fallback + cursor + single first-poll) is still missing.

Follow-up (second pass): still **RECOMMEND: FIX**. INV-OP-W9 is still not closed; additional items: 1. **High — `error = %e` still embeds RPC URLs.** `packages/operator/src/rpc_fallback.rs` sanitizes the `rpc` field but logs `error = %e`. Hyper/reqwest `Display` commonly includes `https://host/v2/<key>`. The all-endpoints `eyre!` interpolates that last error too. Strip URLs from error Display (or log a classified code only). 2. **High — more raw `rpc = %url` sites in the same writer.** Besides enumeration: cancel-window warn and `verify_evm_deposit_on_chain` info/warn (`writers/evm.rs` ~184, 934, 975, 997, 1006). Watcher init `primary_rpc = %rpc_urls[0]` is pre-existing but the same invariant. 3. **Medium — jitter is deterministic (INV-OP-W7).** Seeds are `chain_id ^ chunk_start` / hash bytes (`writers/evm.rs`). Co-scheduled operators retry in lockstep. Mix in process-start entropy. Consensus-head failure seed `chain_id ^ Instant::now().elapsed()` on a just-created Instant is ~0. 4. **Medium — event-poll verifies ignore `WRITER_MAX_VERIFY_PER_CYCLE`.** Enumeration is capped; the event loop verifies every `WithdrawSubmit` in the chunk and invalidates negative-retry. A gas-funded burst in one chunk is uncapped for that cycle. Count event-path verifies toward the cap (or a sibling cap). 5. **Medium — full-cache eviction picks earliest `next_retry`**, which re-queues those hashes as unknown next cycle. Prefer `inserted_at` FIFO under size pressure. 6. **Should-fix:** empty/wrong-chain fallback `eth_getLogs` success advances the event cursor with no runtime `eth_chainId` check. Enumeration is the backstop; do not treat empty fallback logs as observed without a verified endpoint. Fail-closed `verify_deposit_on_source` is unchanged. Failed log ranges are still not skipped. A writer-level mock livelock test (primary blockNumber OK, logs 429, fallback + cursor + single first-poll) is still missing.
PlasticDigits commented 2026-08-31 11:24:19 +00:00 (Migrated from gitlab.com)

added 1 commit

  • 0d58eacd - fix(operator): close INV-OP-W9 sanitizer and remaining writer review items (#138)

Compare with previous version

added 1 commit <ul><li>0d58eacd - fix(operator): close INV-OP-W9 sanitizer and remaining writer review items (#138)</li></ul> [Compare with previous version](/PlasticDigits/cl8y-bridge-monorepo/-/merge_requests/20/diffs?diff_id=1993923749&start_sha=fd4edddbd997600f9bf322552a1a7ea07b23f9f1)
PlasticDigits commented 2026-08-31 11:24:44 +00:00 (Migrated from gitlab.com)

Review follow-up (INV-OP-W9 and remaining !160 items)

Addressed both review passes on this MR. Fail-closed verify_deposit_on_source is unchanged. Failed log ranges are still not skipped. Commit 0d58eac.

Review items completed

Item What landed
High — path API keys in sanitize_rpc_endpoint Logs now emit scheme://host[:port] only. Alchemy /v2/<key> and Infura /v3/<id> are dropped.
High — error = %e embeds RPC URLs New sanitize_rpc_error strips URL tokens from hyper/reqwest Display. Operator fallback, writer, watcher, Terra verify, and Solana verify logs use log_rpc / log_rpc_error. All-endpoints eyre! interpolates the sanitized string.
High — raw rpc = %url in writer/watcher Enumeration, cancel-window, verify_evm_deposit_on_chain, watcher primary_rpc, Terra evm_rpc, Solana verify.
Medium — deterministic jitter (INV-OP-W7) process_jitter_entropy() (pid + ASLR + start nanos) is mixed into every jittered_exponential_backoff seed. Consensus-head failure no longer seeds from Instant::now().elapsed() (~0).
Medium — event-poll ignores WRITER_MAX_VERIFY_PER_CYCLE CycleVerifyBudget is shared by enumeration and event poll, reset each process_pending cycle. Event path acquires a slot before invalidate/verify.
Medium — full-cache eviction by next_retry Evicts earliest inserted_at (FIFO) under size pressure.
Should-fix — empty wrong-chain fallback logs Successful eth_getLogs (including empty) requires eth_chainId match (confirm_rpc_chain_id) before the caller may treat the range as observed.
Missing writer-level livelock test writer_livelock_primary_blocknumber_ok_logs_429_fallback_advances_cursor_once plus empty_wrong_chain_fallback_logs_do_not_succeed.

Docs/invariants/skill: docs/OPERATOR_WRITER_INVARIANTS.md (INV-OP-W3, W4, W7, W9), docs/operator.md, docs/testing.md, docs/deployment-guide.md, packages/operator/.env.example, skills/agent-operator-evm-writer-rpc.md.

Tests run (twice: after implement, after clippy)

  • cd packages/operator && cargo test --bins — 89 passed (was 83)
  • cd packages/multichain-rs && cargo test rpc_fallback — 19 passed
  • cd packages/operator && cargo clippy -- -D warnings — clean
  • Pre-commit hook (fmt, clippy, frontend typecheck/lint, gitleaks) — passed on commit

Checklist for reviewers

  • sanitize_rpc_endpoint("https://eth-mainnet.g.alchemy.com/v2/SECRET") == https://eth-mainnet.g.alchemy.com (path key gone)
  • sanitize_rpc_error on a hyper error sending request for url (https://host/v2/KEY) does not contain KEY
  • Operator logs for enumeration / cancel-window / source verify / watcher init never print raw RPC URLs
  • Primary eth_getLogs 429 + fallback success: cursor advances; first-poll info log only once (writer_livelock_* test)
  • Fallback that returns empty logs on the wrong eth_chainId does not succeed (cursor not advanced)
  • Two operators with the same chain-id seed do not share identical backoff (process entropy)
  • Event-poll WithdrawSubmit burst cannot exceed WRITER_MAX_VERIFY_PER_CYCLE when combined with enumeration in the same cycle
  • Negative-retry cache at cap evicts oldest inserted_at, not soonest next_retry
  • WRITER_MAX_VERIFY_PER_CYCLE / WRITER_POLL_INTERVAL_MS in production env match documented bounds
  • Source verification still fail-closed (unknown srcChain → no approve)

Not done (infra / out of scope)

  • Operator restart against live Anvil with a real Postgres + deployed bridge (lookback-once soak). Covered by cursor unit tests + mock livelock test; not a full process restart.
  • Fallback soak against a real provider pair (primary 429 on logs, second URL healthy). Mock JSON-RPC covers the same RPC shape.
  • Regression E2E Terra→EVM / EVM→EVM / Solana→EVM when full infra is up. Fail-closed verification and enumeration were not weakened.
  • On-chain bounding of attacker-created unapproved withdrawals. This MR still only stops retry amplification; the active/pending set size remains a contract concern (#139 / !158).

No further follow-up from this pass beyond those infra soaks when a stack is available.

## Review follow-up (INV-OP-W9 and remaining !160 items) Addressed both review passes on this MR. Fail-closed `verify_deposit_on_source` is unchanged. Failed log ranges are still not skipped. Commit `0d58eac`. ### Review items completed | Item | What landed | |---|---| | **High — path API keys in `sanitize_rpc_endpoint`** | Logs now emit `scheme://host[:port]` only. Alchemy `/v2/<key>` and Infura `/v3/<id>` are dropped. | | **High — `error = %e` embeds RPC URLs** | New `sanitize_rpc_error` strips URL tokens from hyper/reqwest Display. Operator fallback, writer, watcher, Terra verify, and Solana verify logs use `log_rpc` / `log_rpc_error`. All-endpoints `eyre!` interpolates the sanitized string. | | **High — raw `rpc = %url` in writer/watcher** | Enumeration, cancel-window, `verify_evm_deposit_on_chain`, watcher `primary_rpc`, Terra `evm_rpc`, Solana verify. | | **Medium — deterministic jitter (INV-OP-W7)** | `process_jitter_entropy()` (pid + ASLR + start nanos) is mixed into every `jittered_exponential_backoff` seed. Consensus-head failure no longer seeds from `Instant::now().elapsed()` (~0). | | **Medium — event-poll ignores `WRITER_MAX_VERIFY_PER_CYCLE`** | `CycleVerifyBudget` is shared by enumeration and event poll, reset each `process_pending` cycle. Event path acquires a slot before invalidate/verify. | | **Medium — full-cache eviction by `next_retry`** | Evicts earliest `inserted_at` (FIFO) under size pressure. | | **Should-fix — empty wrong-chain fallback logs** | Successful `eth_getLogs` (including empty) requires `eth_chainId` match (`confirm_rpc_chain_id`) before the caller may treat the range as observed. | | **Missing writer-level livelock test** | `writer_livelock_primary_blocknumber_ok_logs_429_fallback_advances_cursor_once` plus `empty_wrong_chain_fallback_logs_do_not_succeed`. | Docs/invariants/skill: `docs/OPERATOR_WRITER_INVARIANTS.md` (INV-OP-W3, W4, W7, W9), `docs/operator.md`, `docs/testing.md`, `docs/deployment-guide.md`, `packages/operator/.env.example`, `skills/agent-operator-evm-writer-rpc.md`. ### Tests run (twice: after implement, after clippy) - `cd packages/operator && cargo test --bins` — **89 passed** (was 83) - `cd packages/multichain-rs && cargo test rpc_fallback` — **19 passed** - `cd packages/operator && cargo clippy -- -D warnings` — clean - Pre-commit hook (fmt, clippy, frontend typecheck/lint, gitleaks) — passed on commit ### Checklist for reviewers - [ ] `sanitize_rpc_endpoint("https://eth-mainnet.g.alchemy.com/v2/SECRET")` == `https://eth-mainnet.g.alchemy.com` (path key gone) - [ ] `sanitize_rpc_error` on a hyper `error sending request for url (https://host/v2/KEY)` does not contain `KEY` - [ ] Operator logs for enumeration / cancel-window / source verify / watcher init never print raw RPC URLs - [ ] Primary `eth_getLogs` 429 + fallback success: cursor advances; first-poll info log only once (`writer_livelock_*` test) - [ ] Fallback that returns empty logs on the **wrong** `eth_chainId` does **not** succeed (cursor not advanced) - [ ] Two operators with the same chain-id seed do not share identical backoff (process entropy) - [ ] Event-poll `WithdrawSubmit` burst cannot exceed `WRITER_MAX_VERIFY_PER_CYCLE` when combined with enumeration in the same cycle - [ ] Negative-retry cache at cap evicts oldest `inserted_at`, not soonest `next_retry` - [ ] `WRITER_MAX_VERIFY_PER_CYCLE` / `WRITER_POLL_INTERVAL_MS` in production env match documented bounds - [ ] Source verification still fail-closed (unknown `srcChain` → no approve) ### Not done (infra / out of scope) - Operator restart against **live Anvil** with a real Postgres + deployed bridge (lookback-once soak). Covered by cursor unit tests + mock livelock test; not a full process restart. - Fallback soak against a **real** provider pair (primary 429 on logs, second URL healthy). Mock JSON-RPC covers the same RPC shape. - Regression E2E Terra→EVM / EVM→EVM / Solana→EVM when full infra is up. Fail-closed verification and enumeration were not weakened. - On-chain bounding of attacker-created unapproved withdrawals. This MR still only stops **retry amplification**; the active/pending set size remains a contract concern (#139 / !158). No further follow-up from this pass beyond those infra soaks when a stack is available.
PlasticDigits (Migrated from gitlab.com) merged commit 6b40cc5819 into main 2026-08-31 12:11:23 +00:00
PlasticDigits commented 2026-08-31 12:11:25 +00:00 (Migrated from gitlab.com)

mentioned in commit 6b40cc5819

mentioned in commit 6b40cc5819cfd0b0d147c4f524d2fd309ee6dc1d
PlasticDigits commented 2026-08-31 12:39:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #138

mentioned in issue #138
PlasticDigits commented 2026-08-31 12:39:32 +00:00 (Migrated from gitlab.com)

mentioned in issue #140

mentioned in issue #140
Sign in to join this conversation.
No description provided.