next: exponentiation, modulo, chat API and webhooks #10

Open
clawbot wants to merge 9 commits from next into main
Collaborator

Merges next into main: everything #2 asked for, plus two fixes.

On the branch:

  • An HTTP API beside the bot: list its chats, read a chat's recent messages, send a message, and register, list and remove webhooks on a chat; each new incoming message is POSTed to that chat's webhooks. Every request needs the bearer credential from the file named by API_TOKEN_FILE; without one configured, every request is refused.
  • The calculator takes ^ (or **) for powers and % for modulo, and answers exact results past a double's range, such as 2^1200 (#16).
  • cmd/simplexcalc/ is a single main.go; the command tree lives in internal/cli (#8).
  • The README clones from sneak/simplexcalc (#13).
  • A plain docker build . stamps the git tag or short commit instead of dev; .git goes into the build context without its config (#21).

Deploying: nothing is running. The Docker wipe on sandcastle on 2026-09-29 removed both the simplexcalc container and the simplexcalc-data volume, so the next deploy gets a new bot address. To deploy: build, write a credential onto the volume, and run with -p and -e API_TOKEN_FILE=... as the README shows. The volume also holds api-token and webhooks.json; both are secret and both belong in backups.

Behaviour change: a number too large or too small to hold exactly, such as 1e-1300 (which main answers as 0), is now refused rather than answered wrongly.

Model: opus-5-5

Merges `next` into `main`: everything https://git.eeqj.de/sneak/simplexcalc/issues/2 asked for, plus two fixes. **On the branch:** - An HTTP API beside the bot: list its chats, read a chat's recent messages, send a message, and register, list and remove webhooks on a chat; each new incoming message is POSTed to that chat's webhooks. Every request needs the bearer credential from the file named by `API_TOKEN_FILE`; without one configured, every request is refused. - The calculator takes `^` (or `**`) for powers and `%` for modulo, and answers exact results past a double's range, such as `2^1200` (https://git.eeqj.de/sneak/simplexcalc/issues/16). - `cmd/simplexcalc/` is a single `main.go`; the command tree lives in `internal/cli` (https://git.eeqj.de/sneak/simplexcalc/issues/8). - The README clones from `sneak/simplexcalc` (https://git.eeqj.de/sneak/simplexcalc/issues/13). - A plain `docker build .` stamps the git tag or short commit instead of `dev`; `.git` goes into the build context without its `config` (https://git.eeqj.de/sneak/simplexcalc/issues/21). **Deploying:** nothing is running. The Docker wipe on sandcastle on 2026-09-29 removed both the `simplexcalc` container and the `simplexcalc-data` volume, so the next deploy gets a new bot address. To deploy: build, write a credential onto the volume, and run with `-p` and `-e API_TOKEN_FILE=...` as the README shows. The volume also holds `api-token` and `webhooks.json`; both are secret and both belong in backups. **Behaviour change:** a number too large or too small to hold exactly, such as `1e-1300` (which `main` answers as `0`), is now refused rather than answered wrongly. Model: opus-5-5
clawbot added 1 commit 2026-09-29 01:52:03 +02:00
Move the command tree into internal/cli (closes #8)
check / check (push) Successful in 1m40s
09a5caa8ab
`cmd/simplexcalc/` now holds only `main.go`, whose body is one call, `os.Exit(cli.Main(version))`, as the Go style guide requires. The command tree and the `run` and `version` commands moved unchanged into `internal/cli`, with tests for `version` and for an unknown command. Output streams, exit statuses and the `-X main.version` build flag are as before.

Disclosure: the comment in `execute` saying cobra has already printed the error is false, since the root command silences cobra's errors; it moved unchanged and is left for a follow-up.

Model: opus-5-5
clawbot added the merge-ready label 2026-09-29 01:54:41 +02:00
sneak was assigned by clawbot 2026-09-29 01:54:41 +02:00
clawbot added 1 commit 2026-09-29 04:28:17 +02:00
The calculator now takes `^` (or `**`) for powers and `%` for modulo. Powers bind tighter than a sign on their left and group to the right, so `-2^2` is `-4` and `2^3^2` is `512`; `%` sits with `*` and `/` and takes the sign of the divisor. A small parser of our own replaces `go/parser`, which cannot express `^`; `go/constant` still computes.

A whole-number exponent is exact; a fractional one is computed in float64. Every number is held as a fraction under 4096 bits, and a float64 result must be a normal double, so one message cannot stall the bot; anything else gets "That needs a number too large or too small for me."

Disclosure: tiny values below about 2.2e-308, which `next` answered, are now refused.

Model: opus-5-5
clawbot added 1 commit 2026-09-29 04:55:51 +02:00
The bot now serves an HTTP API on `PORT` (default 8080) beside the chat client, whose WebSocket stays on 127.0.0.1 inside the container. Every request needs `Authorization: Bearer` with the credential from the file named by `API_TOKEN_FILE`, compared in constant time; with no credential configured every request is refused, `OPTIONS *` included. `GET /api/v1/chats` lists the bot's chats. Responses carry the security headers from the repository policies; bodies, requests and the server are time- and size-bounded. The chat client stops only after the API has finished its requests.

Disclosures: `contact_deleted` is an extra field; 404 and 405 answer in JSON; requests net/http cannot parse are refused by net/http without the security headers; three gosec findings are suppressed as false positives.

Model: opus-5-5
clawbot added 1 commit 2026-09-29 05:14:02 +02:00
README: clone from sneak/simplexcalc (closes #13)
check / check (push) Successful in 1m38s
8f0906d677
The repository moved to `sneak/simplexcalc`; the Getting Started clone command still named `clawbot/simplexcalc`, which only redirects. It was the only mention of the old location.

Model: opus-5-5
clawbot added 1 commit 2026-09-29 07:33:37 +02:00
`GET /api/v1/chats/{id}/messages?count=N` returns a chat's recent messages, oldest first (`count` 1 to 100, default 20), and `POST` to the same path sends a text message and returns it once the chat client has taken it. Both answer `404` for any id the chats list does not show, looked up in the same contact list. The message record is defined once, for the webhooks to reuse.

Disclosures: items are read five at a time, because one item can be many times its text and 100 at once could pass the 16 MiB read limit; text too long gets `413`, a contact who deleted the chat `409` (unverified for one still connecting); a query the server cannot read gets its own `400`.

Model: opus-5-5
clawbot added 1 commit 2026-09-29 08:38:14 +02:00
An external app can register webhooks on a chat (`POST /api/v1/chats/{id}/webhooks` with a URL), list them, and remove one. Registering the same URL again returns the existing registration. Registrations are kept in `$DATA_DIR/webhooks.json`, rewritten whole on each change through a file created 0600, synced and renamed, so a crash leaves the old file or the new one; a file present but unreadable stops startup. Delivery of incoming messages is the next unit.

Disclosures: the JSON body reading is now one helper shared with the send endpoint; gosec G304 is suppressed on reading the webhooks file; the 0600-from-creation claim rests on `os.CreateTemp`'s source, not a system-call trace.

Model: opus-5-5
clawbot added 1 commit 2026-09-29 09:56:57 +02:00
`2^1200` was refused although the calculator computed it exactly: writing a result went through a float64 and refused anything outside a double's range. An exact result under the 4096-bit limit is now written from its exact value, rounded to 17 significant digits past that range, and a fractional power of such a number is computed by first bringing its base into range with square roots taken from the exact value. Results computed in float64 must still be normal doubles; past the 4096-bit limit is still refused.

Disclosures: `1e-310` is answered again, reversing a call made in PR 11; a fractional power carries `math.Pow`'s rounding, which for a large base can reach the last digits shown.

Model: opus-5-5
clawbot added 1 commit 2026-09-29 10:23:27 +02:00
Webhooks: post each new incoming message (closes #7)
check / check (push) Successful in 2m3s
fa2aa09769
Each message a contact sends in a direct chat is posted to each webhook registered on that chat: `POST`, `Content-Type: application/json`, body `{"chat_id":N,"message":{...}}` with the same record the messages endpoint returns. The event handler only queues the message; four goroutines post it, one attempt each with a 10-second timeout and no retries, and with 100 already waiting a message is dropped and logged, so a slow webhook never delays the bot's replies. Posting stops last, after the chat client.

Disclosures: redirects are not followed; a logged failure can name the webhook's host but never its path, query or credentials; posts still in flight at shutdown are abandoned and logged.

Model: opus-5-5
clawbot added 1 commit 2026-10-02 08:14:01 +02:00
A plain `docker build .` of a clone stamped `dev`: `.dockerignore` left
out `.git` and the build stage declared `ARG VERSION=dev`. `.git` now
reaches the build context without `.git/config`, which can hold a
credential, and the build stage takes the VERSION build argument when
given, otherwise `git describe --tags --always`. A context that carries
`.git` but yields no version fails the build.

`script/docker` is replaced with the current canonical copy, which
passes the version it derives on the host.

Model: opus-5-5
All checks were successful
check / check (push) Successful in 54s
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin next:next
git checkout next
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/simplexcalc#10