Repository navigation
Mini App API: auth, group config, audit trail — plus the brand and contributing docs - #11
Merged
Merged
Conversation
…nlisted The spec row carried an empty trigger tuple, so the parser's three spellings (/transcribe, /transcrever, /transcribir) resolved to no feature at all and the generated command tables reported the command as "Coming" while it had been live since M3. Regenerates sandbox.config.json and the progress data off the corrected spec; `cb.py status` reports no inconsistencies in either direction. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
The docs site looked like a Fumadocs default while the bot and the web hub share a face and a colour scheme. `app/theme.css` now holds one ramp, sampled from the avatar (cocoa #382602, fur #9C834D, blush #FFB5D8) and from the hub's globals.scss (gold #C48B17, caramel #955714, cream #F5D9BC, its status trio), plus the hub's three type faces in the same roles. Fumadocs' --color-fd-* variables are remapped onto those tokens for both themes, so no component spells a hex code any more. - brand assets reused from COOKIEBOT-WebHub: the avatar as the nav mark, the landing hero and the README header; the circuit ornaments masked to a token colour rather than pasted in as artwork - `asset()` prefixes NEXT_PUBLIC_BASE_PATH: a static export does not rewrite <img src>, so an unprefixed asset 404s only on Pages - status colours get -ink variants; the hub's fills are tuned for dark backgrounds and drop under 4.5:1 as small text on cream - <CommandCards>, the card rendering of the generated alias table, for pages that explain a command rather than list it - OG cards rendered in the same palette instead of Fumadocs' black Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
…we told groups The user-facing section covered setup, the config menu, moderation, privacy and troubleshooting — six pages for a bot with 48 commands, so most of what it does was documented only as a row in a generated table. It now has one page per family (welcome/rules/language, fun, utilities, AI, giveaways and events, cross-posting, owner commands), written from the Gherkin scenarios and the handlers rather than from memory. Reading those sources corrected four things the site had wrong: - sticker floods are counted per *group*, not per member, and admins are not exempt — one person's flood gets the next person's sticker deleted - /dice obeys Utility Functions and /birthday obeys Fun Functions, which is where v1 put them - an unrecognised command is not silence: it becomes an image search - privacy now lists what actually leaves the deployment (the model provider, transcription, reverse search, agify/genderize, the anti-spam services) The README follows the same shape: what the bot does first, the rewrite second. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
… loop Reporting a problem meant a blank issue box, which reliably arrives without the command, the deployment or what the person expected. Four forms replace it, each asking for two or three facts and nothing more: something is broken, v2 behaves differently from v1, an idea, the docs are wrong. Blank issues are off; the config links the docs, the troubleshooting page and the private advisory form, so a security report has an obvious place that is not a public issue. The parity form exists because "a command changed its wording" is the most valuable report this project gets and had nowhere to go. CONTRIBUTING.md says the part that was never written down: reports and ideas are contributions, half-formed ideas are welcome, and running a large group makes you the only person who can see certain defaults being wrong. It then documents the loop — branch, scenario first, implement, spec, docs, gate, PR — and the Conventional Commits shape cliff.toml already assumes. The development guide gains the same loop and the commit-message table, since that is where a contributor is standing when they need it, and a pull request template asks the three questions review always asks anyway. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
Two tables one shard rule apart. `refresh_tokens` is a reference table — a session is not a group's data and any node may redeem one — and stores SHA-256 hashes, so a database dump is not a set of live sessions. `group_audit_events` is distributed on `group_id` like everything else a group owns, keyed by a v7 UUID, which makes the newest-first page a keyset read on one shard with no second index on `ts`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
`/login` authenticates a login-widget payload, which a page opened inside Telegram does not have. It has `initData`. So `/oauth2/token` takes both, as two extension grants, plus `refresh_token` — one endpoint, RFC 6749 error bodies, and RFC 7009 revocation beside it. The access token is the same RS256 JWT `/login` mints, signed by the same keys and published by the same JWKS, so nothing downstream learns a second format. What is new is `scope` and `aud`, a fifteen-minute life, and a refresh token behind it that rotates on every use: presenting a spent one revokes its whole family, because there is no way to tell a buggy client from a thief and letting both refresh is the worse failure. `/login` is untouched. Its token carries no `scope`, and `security` reads that as read-only — exactly what the shipped console could do before this existed. The discovery document gained the token endpoint, the grants and the scopes; its five v1 keys are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
v1 kept no trail at all, which was survivable while the Telegram menu was the only way to change a setting. It is about to stop being the only way, and a settings surface with two front ends and no history is how a group ends up unable to answer that question. Every write records the fields that actually moved, with their old and new values, the actor and the surface — `telegram` for the `/config` menu, `miniapp` for the HTTP API — so one trail reads the same whichever way an admin changed something. An anonymous admin produces a row with no actor: Telegram hands the bot GroupAnonymousBot, and a guess would be worse than a gap. A failed audit write never fails the action it describes. The setting changed; losing the evidence of it is bad, and undoing it because the evidence was lost is worse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
Everything `/config` can do in a private chat, an admin can now do from the
Mini App: read the group's settings, change them, set the rules and the welcome
message, read back who changed what.
Three rules shape it. The group is the authorisation boundary — every path
carries `{group_id}`, and a caller who does not administer it gets 404, the
same answer an unknown group gets, so a logged-in stranger cannot walk chat
ids. Reading and writing are different scopes, which is why a scope-less
`/login` token stays read-only. And every write leaves an audit row.
`PATCH`, not `PUT`: a form that round-trips every column would overwrite the
change another admin made while it was open. The rules and welcome text go
through the same `cb_core.group_texts` upsert `/newrules` and `/newwelcome`
now use, so a body set here shows up in the chat immediately — the handlers
were rewritten onto it rather than given a second table to disagree with.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
The Mini App will be built from a generated client, and a generator is only as good as the document it reads. Every route it touches answered `type: object` — true, useless, and an invitation to type the shapes out a second time by hand and let the two drift. So each one now names a model: the token and error bodies, `/me`, the config, rules, welcome and audit responses, and the four analytics reads. The token endpoint's request body is declared under both content types, because it takes form-encoded *and* JSON and a schema that admitted only one would be a lie about what the handler accepts. The refusals are documented too — 401, 403 and 404 mean three different things here, and a client that cannot tell "log in again" from "you do not administer this group" will retry the wrong one. `test_openapi.py` holds the line: the path list is a whitelist, so a new endpoint either describes itself or fails the suite. `/` and `/login` stay exempt; a response model on either would filter v1's error bodies back out. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
Three features enter the spec — `x_miniapp_auth`, `x_group_config_api`, `x_audit_log` — so the progress board, the feature index and the README's counts move with them rather than being edited by hand later. The Mini App API page is the one a client author reads end to end: the token flow with its three grants, what the access token carries, every endpoint behind it with its scope, the audit trail's shape, the error table, and the operator's settings. It points at `/openapi.json` for the exact types. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two things rode this branch: the docs work it was named for, and the Mini App API built on top of it.
The API a Mini App can use
x_miniapp_auth—/oauth2/tokenwith three grants (TelegraminitData, the login widget's payload,refresh_token) and/oauth2/revoke. Same RS256 keys and JWKS/loginalready publishes, so one resource server verifies both; what is new isscope,aud, a fifteen-minute access token and a refresh token that rotates on every use. A replayed refresh token revokes its whole family./loginis untouched and its scope-less token stays read-only.x_group_config_api— every/configsetting plus the rules and welcome text, readable and writable over HTTP by a group's admins.groups:read/groups:write, PATCH semantics so two admins do not overwrite each other, and 404 (not 403) for a group you do not administer, so a logged-in stranger cannot walk chat ids.x_audit_log— every settings change leaves a row, from both surfaces.group_audit_eventsis distributed ongroup_idwith a UUIDv7 key, so the newest-first page is a keyset read on one shard. A failed audit write never fails the action it describes.Migration
0010adds both tables:refresh_tokensas a reference table storing SHA-256 hashes,group_audit_eventsdistributed like everything else a group owns.OpenAPI
Every endpoint the Mini App calls now names a response model instead of answering
type: object, the token endpoint declares its request body under both content types it accepts, and the refusals (401 / 403 / 404 mean three different things here) are documented per route.test_openapi.pykeeps it that way — the path list is a whitelist, so a new endpoint either describes itself or fails the suite.Docs
The Mini App API page, a page per feature family under Using Cookiebot, the contributing guide and issue forms, the avatar's palette as design tokens, and the board moving to 65/65.
Checks
python scripts/cb.py checkgreen locally: 3391 passed, 44 skipped; mypy clean on 174 files; the compiled-module bench clears its gate; spec, docs and tests agree in both directions.🤖 Generated with Claude Code
https://claude.ai/code/session_0136T2HD8LrV65kGzZfhiM3s