Per-group audit trail
net-new: v1 kept no trail at all. group_audit_events is distributed on group_id (migration 0010) with a UUIDv7 key, so the page is a keyset read on one shard. Written by both surfaces - the Mini App's config endpoints and the Telegram config menu, /newrules and /newwelcome - and a failed audit write never fails the action it describes
- v1 scenarios
- —
- Ported
- —
- Green
- —
- Failing
- —
net-new: v1 kept no trail at all. group_audit_events is distributed on group_id (migration 0010) with a UUIDv7 key, so the page is a keyset read on one shard. Written by both surfaces - the Mini App's config endpoints and the Telegram config menu, /newrules and /newwelcome - and a failed audit write never fails the action it describes
What it does
Every admin action on a group's settings leaves a row: who did it, from which
surface, what the value was before, and what it is now. GET /groups/{group_id}/audit reads it back, newest first, keyset-paginated and
filterable by action or actor.
Both surfaces write to it — the Mini App's config endpoints and, on the Telegram
side, the /config menu, /newrules and /newwelcome — so the trail answers
"who turned the captcha off" whichever way it was turned off.
Behaviour that must not change
Rows quote, they do not paraphrase. before and after hold the fields
that actually changed with their values; the human-readable summary sits
beside them, never instead of them.
A save that changed nothing writes nothing. An admin who opens a form and presses save should not produce a row that reads like they rewrote every setting.
A failed audit write never fails the action it describes. By the time the
row is written the setting has already changed; a 500 at that point would tell
the client their change did not land when it did. The loss is loud instead —
log.error plus cb_audit_write_failures_total.
An anonymous admin has no actor. Telegram gives GroupAnonymousBot rather
than an account, so the row records what changed with actor_user_id null,
which is the truth rather than a guess.
How to verify it
packages/cb-core/tests/test_audit.py covers the diff and the
swallowed-failure path; qa/integration/test_audit_log.py covers the jsonb
round trip, keyset paging over seven rows in three pages, per-group isolation
and the Task Count: 1 plan that keeps the page single-shard.
Per-group analytics endpoints
four reads over the rollups: daily, commands, llm, summary. Bearer token from /login plus group_admins membership, 404 (not 403) for a group you do not administer, bounded windows, no fleet-wide endpoint - every query is single-shard
Group settings over HTTP, admin-gated
every /config setting plus the rules and welcome text, readable and writable by a group's admins from the Mini App. groups:read / groups:write scopes, 404 (not 403) for a group you do not administer, PATCH semantics so two admins do not overwrite each other, and the same cb_core.group_texts upsert the Telegram handlers use