mirror of
https://github.com/kuhyx/signal-bot.git
synced 2026-11-10 19:17:02 +01:00
docs: record the traps this session hit
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b70730fc7a
commit
e102e413ff
@ -16,6 +16,23 @@ Add the new section at the top when you bump `RELEASE_ORDINAL`. Anything that
|
||||
is not a `##` heading or a `-` bullet under one is ignored, so prose between
|
||||
releases is free.
|
||||
|
||||
**A version bump is a publish, not a checkpoint.** Restarting the service after
|
||||
one posts these bullets into every watched group, including the real one. So
|
||||
exercise the feature in a test group — `testbot`, `catbot`, `finaltestbot`,
|
||||
each just you and the bot — *before* bumping `RELEASE_ORDINAL`, tagging or
|
||||
restarting. To try something at the current version without announcing, leave
|
||||
`RELEASE_ORDINAL` alone and restart. Green unit tests and a green
|
||||
`scripts/e2e.sh` are **not** sufficient evidence: `e2e.sh` runs against the
|
||||
local fake, so it proves the wiring and nothing about how Signal renders the
|
||||
result or how the real upstream API behaves. 0.0.3 shipped a `!gekon` that
|
||||
passed every gate and posted the same photo every time, because the one thing
|
||||
nobody had done was call the real endpoint twice.
|
||||
|
||||
**Write bullets the group can notice.** The audience is people in a chat, not
|
||||
this repo: describe what changes for them. 0.0.1 shipped a bullet about
|
||||
`/healthz`, an endpoint bound to 127.0.0.1 that nobody in a Signal group can
|
||||
reach, and it was rightly called out.
|
||||
|
||||
## 0.0.4 — Ewaryst
|
||||
|
||||
- `!gekon` pokazuje teraz za każdym razem innego gekona — wcześniej wrzucał
|
||||
|
||||
46
DOCS-latency.md
Normal file
46
DOCS-latency.md
Normal file
@ -0,0 +1,46 @@
|
||||
# Latency: where the 3.7 seconds actually go
|
||||
|
||||
Measured on the live bot, 2026-08-29, for one real `!cat` in the production
|
||||
group. Every number here came from `journalctl --user -u signal-bot.service`,
|
||||
not from a model of the system.
|
||||
|
||||
```
|
||||
22:59:37.984 running command cat
|
||||
22:59:38.370 GET api.thecatapi.com/…/search 200 386 ms
|
||||
22:59:39.054 GET s3…/MjA2MDgxMA.jpg 200 684 ms
|
||||
22:59:41.702 POST localhost:9922/v2/send 201 2648 ms
|
||||
```
|
||||
|
||||
Fetching the picture — the JSON lookup plus the download — is **1.07 s of the
|
||||
3.7 s**. `POST /v2/send` on its own is **2.65 s**, and that is the
|
||||
signal-cli-rest-api container uploading the attachment to Signal. It is 72% of
|
||||
the wall clock and none of it is this repo's code.
|
||||
|
||||
## The consequence for the latency release
|
||||
|
||||
The plan proposed an image prefetch pool as "the single biggest win": keep N
|
||||
pre-fetched, pre-base64'd images per command in memory so a command costs one
|
||||
send instead of *fetch JSON → download → encode*. The measurement says that
|
||||
buys 3.7 s → about 2.7 s. Worth having, but it is not the biggest win and it
|
||||
does not get anywhere near a sub-second reply, because the send is untouched.
|
||||
|
||||
**Do not design the latency release on the plan's assumption.** Before
|
||||
building anything, measure these two separately:
|
||||
|
||||
1. A **text-only** reply, end to end. That isolates `POST /v2/send` without an
|
||||
attachment and says how much of the 2.65 s is upload versus fixed overhead.
|
||||
2. Whether signal-cli can **reuse an already-uploaded attachment id**, so a
|
||||
repeated image costs a reference rather than a fresh upload. If it can,
|
||||
that is the real win and it makes the prefetch pool far more valuable,
|
||||
because the pool could hold ids instead of bytes.
|
||||
|
||||
Head-of-line blocking is a separate, confirmed problem and does not need
|
||||
re-measuring: `app.py` awaits each frame to completion, so one slow upstream
|
||||
fetch delays every other message behind it. Dispatching each frame as its own
|
||||
task under a bounded semaphore fixes that regardless of what the send costs.
|
||||
|
||||
## Rule
|
||||
|
||||
An upstream latency claim gets measured before it gets designed around. The
|
||||
prefetch-pool assumption survived a whole planning pass and was wrong by more
|
||||
than a factor of two in the direction that mattered.
|
||||
@ -61,9 +61,18 @@ trusts `source`.
|
||||
|
||||
## From the bot's own account: `syncMessage.sentMessage`
|
||||
|
||||
Because the bot runs as a *linked device*, the account's own outgoing messages
|
||||
When the account is deployed as a *linked device*, its own outgoing messages
|
||||
come back as sync messages. Same fields, one level deeper.
|
||||
|
||||
> **This deployment never receives them.** The bot registers its own number as
|
||||
> its own primary device and has no linked devices, so Signal sends it no copy
|
||||
> of its own traffic. The consequence to know before you try to test anything:
|
||||
> the bot **cannot self-trigger a command**. Posting `!cat` from the bot's
|
||||
> number into a group does nothing whatsoever — a human has to type it, and
|
||||
> `journalctl --user -u signal-bot.service` showing `running command …` is how
|
||||
> you confirm it landed. The parsing below stays because the linked-device
|
||||
> deployment is still supported, not because it fires here.
|
||||
|
||||
```json
|
||||
{
|
||||
"envelope": {
|
||||
@ -99,7 +108,7 @@ were counted as messages. Four consecutive commits (`f1931b8`, `b325126`,
|
||||
## No `groupInfo` means a direct message
|
||||
|
||||
A frame without `groupInfo` arrived in a one-to-one conversation. A linked
|
||||
device receives every one of those on the account, so `counting.watched_group`
|
||||
device would receive every one of those on the account, so `counting.watched_group`
|
||||
rejects them explicitly rather than relying on a falsy comparison. See the
|
||||
`scoping` scenario in `fake_signal/scenarios/`.
|
||||
|
||||
|
||||
24
README.md
24
README.md
@ -201,9 +201,8 @@ connection to Signal is dead, a two-minute timer restarts the container, and
|
||||
Aliases live in `signal_bot/aliases.json`, not in source. Each command is rate-limited
|
||||
per user, per command — the original bot shared one 10-second window across everybody.
|
||||
|
||||
`signal_bot/decorators.py` also ships `daily_limit` and `vote_gated` (run only
|
||||
once N distinct people ask, within a window). Both are generic and tested;
|
||||
attach them to any command.
|
||||
`signal_bot/decorators.py` also ships `daily_limit` and `vote_gated` (run only once N
|
||||
distinct people ask, within a window). Both are generic and tested; attach them to any command.
|
||||
|
||||
## Watching more than one group
|
||||
|
||||
@ -214,15 +213,14 @@ group and the production group at once:
|
||||
GROUP_IDS=group.<base64-of-test>,group.<base64-of-prod>
|
||||
```
|
||||
|
||||
Everything downstream is keyed by group. A command is answered **in the group
|
||||
that asked and nowhere else**; cooldowns, daily limits, votes and the tally
|
||||
are per group, so a `!cat` in the test group neither throttles nor counts
|
||||
against the production one. There is no reply-target setting to get wrong —
|
||||
Everything downstream is keyed by group. A command is answered **in the group that asked and nowhere
|
||||
else**; cooldowns, daily limits, votes and the tally are per group, so a `!cat` in the test group neither
|
||||
throttles nor counts against the production one. There is no reply-target setting to get wrong —
|
||||
`GROUP_ID_SEND` was removed, and setting it now fails startup rather than
|
||||
quietly posting the day's report into one group's chat.
|
||||
|
||||
The `per_group_isolation` scenario is the gate: one person asks in both
|
||||
groups inside a single cooldown window and must get two images, one in each.
|
||||
The `per_group_isolation` scenario is the gate: one person asks in both groups
|
||||
inside a single cooldown window and must get two images, one in each.
|
||||
|
||||
## Daily tally
|
||||
|
||||
@ -237,13 +235,15 @@ Counts live in SQLite and survive a restart.
|
||||
## Develop
|
||||
|
||||
```sh
|
||||
pytest # 368 tests, 100% branch coverage, no network
|
||||
pytest # 377 tests, 100% branch coverage, no network
|
||||
pre-commit run --all-files # ruff ALL + mypy strict + 250-line cap + gates
|
||||
./scripts/e2e.sh # the real bot against the fake server
|
||||
```
|
||||
|
||||
No `# noqa`, no `# type: ignore`, no coverage omits. Wire format:
|
||||
[DOCS-signal-envelopes.md](DOCS-signal-envelopes.md). Releases are named after the popes, in order: [DOCS-changelog.md](DOCS-changelog.md).
|
||||
No `# noqa`, no `# type: ignore`, no coverage omits. Never bump `SCHEMA_VERSION` to add a table —
|
||||
read `signal_bot/schema.py` first; a bump DROPs every table and throws the live tallies away, while
|
||||
`CREATE TABLE IF NOT EXISTS` is additive and safe. Wire format: [DOCS-signal-envelopes.md](DOCS-signal-envelopes.md).
|
||||
Releases are named after the popes, in order, and a bump publishes to the group: [DOCS-changelog.md](DOCS-changelog.md). Latency: [DOCS-latency.md](DOCS-latency.md).
|
||||
|
||||
## Licence
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user