Problem Statement
As the Owner of a personal fork, I am maintaining a public-project-sized Telegram group-management bot even though I operate only one Private instance for groups I personally manage. The repository still carries many unused product domains, PostgreSQL and Redis services, public distribution targets, several monitoring systems, and duplicated CI/release paths. This creates unnecessary dependency updates, schema and cache coordination, background workers, deployment configuration, test cost, and operational failure modes.
The fork needs a deliberately smaller product contract: retain only the Required workflows selected by the Owner, make that contract explicit, and shape the application, state model, container, automation, and documentation around one operator and one production instance.
Solution
Reduce the bot to the explicitly retained commands and automatic behaviors, deleting every other feature rather than merely hiding it. Replace PostgreSQL and Redis with an embedded SQLite database plus in-process ephemeral state. Keep both Telegram polling and webhook delivery, but reduce observability to structured logs and health checks. Deploy one linux/amd64 container from GHCR to one Docker host, with compact verification and publishing workflows instead of the inherited release matrix.
Deliver the reduction as staged, independently green changes: contract and feature reduction; SQLite and in-memory state; runtime and container simplification; then CI/CD and documentation cleanup. Production starts with a fresh SQLite volume. Existing PostgreSQL data and old bot exports are not migrated.
User Stories
- As the Owner, I want the repository optimized for one Private instance, so that I do not maintain portability intended for unrelated operators.
- As the Owner, I want every retained command documented as an explicit Required workflow, so that future cleanup does not depend on guessing which inherited features matter.
- As the Owner, I want all unlisted commands removed from handlers, help, locales, backups, and tests, so that dead product surface does not keep generating maintenance work.
- As a group administrator, I want
/ban to accept an optional duration, so that one command handles both permanent and temporary bans.
- As a group administrator, I want
/unban, so that I can reverse either kind of ban explicitly.
- As a group administrator, I want
/kick, so that I can remove a member without preventing re-entry.
- As a member, I want
/kickme, so that I can ask the bot to remove me from the group.
- As a group administrator, I want
/mute to accept an optional duration, so that one command handles permanent and temporary restrictions.
- As a group administrator, I want
/unmute, so that I can restore chat permissions explicitly.
- As a group administrator, I want a single visible response for ban and mute operations without inline undo buttons, so that recovery happens through explicit commands and callback complexity is reduced.
- As a group administrator, I want
/warn and warning history, so that repeated behavior can be tracked before automatic enforcement.
- As a group member or administrator, I want
/warns, so that warning counts and reasons remain visible.
- As a group administrator, I want
/rmwarn and /resetwarns, so that one or all warnings for a member can be corrected.
- As a group owner, I want
/resetallwarns, so that a group-wide warning reset remains possible after confirmation.
- As a group administrator, I want
/setwarnlimit, so that each group can choose its warning threshold.
- As a group administrator, I want
/warnings to explain the threshold and fixed policy, so that enforcement is auditable.
- As the Owner, I want the warning terminal action fixed to a three-day ban, so that the warn-mode setting and its database state can be removed.
- As a group administrator, I want
/purge, so that I can delete a contiguous message range without maintaining several purge variants.
- As a group administrator, I want to approve or unapprove individual trusted members, so that selected users can bypass automated abuse checks.
- As a group administrator, I want
/approval and /approved, so that exemptions can be inspected and audited.
- As a group administrator, I want configurable anti-flood thresholds, enforcement modes, and cleanup behavior, so that rapid-message abuse remains automatically controlled.
- As a group administrator, I want automatic and manual anti-raid modes with configurable detection and action durations, so that join bursts can trigger a temporary lockdown.
- As a group administrator, I want configurable word and phrase blacklists, so that matching messages are removed and selected moderation actions are applied.
- As a group administrator, I want one canonical command for each blacklist operation, so that duplicate aliases do not inflate help and tests.
- As a group administrator, I want persistent text and media filters, so that a trigger can automatically send configured content.
- As a group administrator, I want filter overwrite confirmation, so that existing automatic responses are not replaced accidentally.
- As a group administrator, I want configurable welcome messages and previous-welcome cleanup, so that joining members receive current group guidance.
- As a group administrator, I want service-message cleanup to remain configurable, so that Telegram join and leave notices can be removed.
- As a group administrator, I want named text/media notes, so that reusable content can be retrieved with
#name.
- As a group administrator, I want note overwrite and bulk-clear confirmation, so that destructive note changes remain deliberate.
- As a group administrator, I want persistent keyword-to-emoji reactions, so that the bot can react automatically to configured phrases.
- As a group member, I want an always-enabled
/report, so that I can notify administrators without @admin interception or report-setting state.
- As a current Telegram group administrator, I want to connect from private chat using
/connect, so that I can run supported group commands remotely.
- As a connected administrator, I want
/connection and /disconnect, so that the current remote context is visible and can be ended explicitly.
- As a group administrator, I want JSON export, import, and reset for retained persistent domains, so that group configuration remains portable and recoverable.
- As the Owner, I want
/stats, /chatinfo, /chatlist, and /leavechat, so that I can inspect and control the Private instance without developer or sudo role delegation.
- As a user, I want
/stat, /id, /tell, /ping, and /info, so that the selected utility workflows remain available.
- As a user, I want simplified
/start, interactive /help, module help links, and /formatting, so that the reduced product is discoverable without inherited promotional pages.
- As the Owner, I want full passive user, membership, chat, and channel tracking, so that retained moderation, connection, backup, and inspection workflows have the identities they need.
- As the Owner, I want SQLite persisted on a Docker volume, so that the Private instance needs no external database service.
- As the Owner, I want ephemeral caches, rate limits, confirmation tokens, and raid counters kept in process memory, so that Redis can be removed.
- As a user, I want expired or pre-restart inline confirmations to fail safely, so that resetting ephemeral state never performs a stale mutation.
- As the Owner, I want structured logs and
/health, so that the container remains operable without tracing, metrics, profiling, or remediation systems.
- As the Owner, I want both polling and webhook support, so that update delivery can change without restoring deleted infrastructure.
- As the Owner, I want queued Telegram updates processed after a restart, so that offline messages are not discarded.
- As the Owner, I want operational dump-chat notices to be optional, so that logs are sufficient unless I explicitly configure
MESSAGE_DUMP.
- As the Owner, I want one non-root
linux/amd64 Alpine image with a writable /data volume, so that production deployment has one supported container path.
- As the Owner, I want pull requests to verify formatting, tests, lint, binary compilation, locale validity, and the production image, so that failures are caught before merge.
- As the Owner, I want passing
main commits published to GHCR as latest and an immutable short-SHA tag, so that deployment and rollback remain simple.
- As the Owner, I want weekly dependency PRs without automatic merging, so that updates remain visible and deliberate.
- As the Owner, I want the commit SHA reported by
--version, so that a running container can be tied to source without manual version bumps.
- As a future maintainer, I want ADRs, a glossary, and current operator documentation, so that the reasons for the reduced design do not have to be rediscovered.
Implementation Decisions
- The product contract is allowlisted: every current command or automatic feature not named in this spec is deleted, not feature-flagged.
- Bans retain
/ban, /unban, /kick, and /kickme. /ban <target> [duration] [reason] recognizes Nm, Nh, Nd, and Nw; omission means permanent. Remove silent/delete variants, /tban, /dkick, /restrict, /unrestrict, and moderation undo callbacks.
- Mutes retain
/mute and /unmute, using the same optional-duration contract. Remove /smute, /tmute, and /dmute.
- Warnings retain
/warn, /warns, /rmwarn, /resetwarns, /resetallwarns, /setwarnlimit, and a simplified /warnings. Remove warning aliases and /setwarnmode. Crossing the threshold applies a 72-hour ban.
- Purges retain only reply-based
/purge. Remove single-delete and two-step range commands and state.
- Approvals retain
/approve, /unapprove, /approval, and /approved. Remove bulk clearing and greeting-driven auto-approval.
- Anti-flood retains its watcher and
/setflood, /setfloodmode, /delflood, and /flood.
- Anti-raid retains its join watcher, expiry worker, callbacks,
/antiraid, /raidtime, /raidactiontime, and /autoantiraid.
- Blacklists retain
/blacklists, /addblacklist, /rmblacklist, /blacklistaction, and /rmallbl; delete their duplicate aliases.
- Filters retain
/addfilter, /rmfilter, /filters, text/media replies, the message watcher, and consume-once overwrite confirmation. Remove aliases and bulk clear.
- Greetings become welcome-only behavior with
/welcome, /setwelcome, /cleanwelcome, and /cleanservice. Remove goodbye state and handlers, /resetwelcome, and /autoapprove.
- Notes retain
/addnote, /clear, /rmnote, /notes, /clearall, #name, media/formatting, and overwrite confirmation. Remove /save, /saved, /get, private-note settings, and all note deep links.
- Reactions retain
/addreaction, /removereaction, /reactions, and the watcher. Remove bulk reset and help-only callbacks.
- Reports become an always-enabled
/report notification path. Remove @admin matching, persistence, configuration, disable checks, and resolution callbacks.
- Connections retain
/connect, /disconnect, /connection, and its control keyboard. Any current group administrator may connect. Remove /reconnect, connection history, /allowconnect, authorization settings, and connection deep links.
- Backup retains
/export, /import, and /reset, but its versioned format and transactional adapters include only antiflood, antiraid, approvals, blacklists, connections, filters, welcome, notes, reactions, and warnings. Old export compatibility is not required.
- Utilities retain
/stat, /id, /tell, /ping, and /info. Owner operations retain /stats, /chatinfo, /chatlist, and /leavechat.
- Remove stored developer and sudo roles and their commands. Bot-wide privileged operations use
OWNER_ID; group actions continue to use Telegram administrator permissions.
- Help retains simplified
/start, interactive /help, retained-module callbacks/deep links, and /formatting. Remove Donate, About, setup/onboarding, upstream promotion, and obsolete module metadata.
- Remove the user-facing Admin module and anonymous-admin routing while preserving shared Telegram permission checks and automatic admin-cache invalidation.
- Delete Captcha, Locks, Rules, Pins, Disabling, and diagnostic-only Anti-spam, including their models, repositories, workers, callbacks, help, locales, configuration, backup adapters, and tests.
- Keep full passive identity tracking but redesign it for a single bot: no bot table or bot-scoped foreign keys. Normalize chat membership instead of storing a PostgreSQL JSONB array.
- Use SQLite through GORM, with a default path of
/data/alita.db. Enable foreign keys, WAL, a busy timeout, and a conservative connection limit suitable for SQLite's single-writer model.
- Replace the inherited PostgreSQL history with one embedded SQLite baseline containing only retained tables. Future ordered embedded migrations are recorded in SQLite. There is no PostgreSQL-to-SQLite migration.
- Normalize warning events as rows and store only the configurable threshold in warning settings. Store naturally structured button payloads as JSON text rather than PostgreSQL JSONB.
- Replace Redis and both cache layers with one concurrency-safe in-process TTL facility. It serves repository read-through caches, admin status, confirmation tokens, overwrite state, backup rate limits, restricted-user state, and flood/raid counters. Confirmation reads are consume-once where mutation follows.
- Ephemeral state is allowed to reset on process restart. Old callback tokens return a user-safe expired/invalid response and never mutate data.
- Retain the persisted anti-raid active-until state so the expiry worker can recover an active lockdown after restart; only detection counters reset.
- Remove OpenTelemetry, Prometheus, database metrics, pprof, background statistics, automatic remediation, and the hourly activity worker.
/stats and /chatlist derive active/inactive status from persisted last_activity at request time.
- Retain structured logging and a
/health endpoint that checks process and SQLite readiness. Keep the CLI health probe for the container health check.
- Retain polling and webhook startup/shutdown paths. Do not request Telegram to drop pending updates.
- Configuration is limited to required
BOT_TOKEN and OWNER_ID; optional SQLITE_PATH, HTTP_PORT, LOG_LEVEL, MESSAGE_DUMP, and USE_WEBHOOKS; and conditional WEBHOOK_DOMAIN plus WEBHOOK_SECRET in webhook mode. Defaults are internal rather than environment-tunable.
- Local builds report version
dev; CI injects the short commit SHA. Remove source version constants, bump scripts, and semantic-release coupling.
- Use one CGO-enabled, multi-stage Alpine production image for
linux/amd64, running as a non-root user with /data writable. Embed migrations in the binary. Remove debug, PR, and GoReleaser image definitions.
- Compose contains one bot service, one named SQLite volume, essential environment settings, port mapping, restart policy, and the health check. PostgreSQL, Redis, local Bot API, and their volumes are removed.
- Keep a simplified development container with Go and SQLite prerequisites.
- Remove Heroku, Render, Railway, Nixpacks, Procfile, tagged release, GoReleaser, Pullfrog, AI-review, pre-commit, dependency auto-merge, and release-changelog configuration.
- Keep weekly Dependabot pull requests without automatic approval or merge.
- CI uses the Go version declared by the module. Pull requests run formatting verification, ordinary and race-appropriate tests, lint, binary build, locale validation, and a production Docker build. Coverage is reported without a percentage gate.
- Passing
main publishes ghcr.io/fullstacksjs/alita-bot:latest and ghcr.io/fullstacksjs/alita-bot:<short-sha>. Deployment is a manual pull and restart on the Docker host.
- Keep the existing Go module/import path to avoid a mechanical rename. Clean repository, container, README, and help metadata to describe this fork while retaining the upstream license and attribution.
- Record the decisions in ADRs for the single Private instance, product allowlist, SQLite/in-process state, and single-host delivery. Maintain the domain glossary and reconcile contributor/operator guidance with the resulting code.
- Deliver in four independently green stages: product contract and feature deletion; SQLite/in-memory state; runtime/container simplification; CI/CD and documentation cleanup.
Testing Decisions
- Tests assert externally observable behavior at the highest practical seam. The primary product seam is a configured dispatcher plus fake Telegram client: it proves the exact command registry, update routing, callback behavior, replies, and Telegram API calls without testing private helper structure.
- The second seam is the retained repository/backup boundary against a temporary real SQLite database. It proves migration, constraints, transactions, persistence, and concurrency behavior without mocking GORM.
- Runtime acceptance uses the real application bootstrap with fake Telegram transport where possible, followed by the production Docker image with a temporary mounted data volume.
- Existing command tests, dispatcher registry tests, repository round-trip tests, migration-chain tests, HTTP server tests, and backup transaction tests are the prior art. They should be consolidated around the reduced contract rather than copied for removed aliases.
- Add one exact command/help contract test that fails if an unapproved command is registered or a retained command is missing.
- Cover permanent and timed ban/mute syntax, reason parsing, invalid duration bounds, permission rejection, and explicit unban/unmute.
- Cover warning creation/removal/reset, configurable thresholds, simplified settings output, and an exact 72-hour terminal ban.
- Cover approved/admin exemptions and enforcement for anti-flood, anti-raid, blacklists, filters, welcome cleanup, notes, reactions, and reports.
- Cover connection authorization using current Telegram administrator status and connection teardown, including safe behavior after cached status invalidation.
- Cover backup export/import/reset for every retained adapter, unknown or removed module rejection, consume-once confirmation, and full transaction rollback when one adapter fails.
- Apply the embedded SQLite baseline to an empty temporary file and reopen it to verify version tracking, foreign keys, and persisted data.
- Exercise concurrent filters, notes, warnings, memberships, reactions, approvals, and connections under the selected SQLite connection/WAL settings, including busy-timeout behavior.
- Simulate process restart by replacing the ephemeral store: persisted state remains; caches, rate limits, confirmations, and counters disappear; stale callbacks fail without mutation.
- Verify polling and webhook startup, conditional webhook validation, queued-backlog processing, optional dump notices, graceful worker shutdown, health readiness, and version output.
- Validate the single English locale as an ordinary Go test: YAML parses, required retained keys exist, and removed-module completeness is not enforced.
- Build the production image in CI, run it as non-root with a temporary
/data volume, verify /health, then restart it and confirm SQLite persistence.
Out of Scope
- Preserving or migrating existing PostgreSQL or Redis data.
- Importing backup formats produced before this reduction.
- Supporting multiple bot instances in one database or process.
- Supporting third-party self-hosting portability beyond the documented Docker-host path.
- ARM64 or multi-architecture images.
- Automatic deployment, host orchestration, or registry-triggered restarts.
- Tagged semantic releases, downloadable desktop binaries, changelog automation, provenance attestations, or release-time vulnerability scans.
- Restoring deleted modules through feature flags or deprecation aliases.
- Replacing GORM with
database/sql.
- Persisting ephemeral confirmation, rate-limit, cache, or raid-detection state across restarts.
- Prometheus, OpenTelemetry, pprof, custom performance remediation, or a public observability API.
- Automated SQLite backup code; the Docker host is responsible for volume snapshots. In-bot JSON export remains available for retained group settings.
- Full Go module/import-path rebranding.
Further Notes
- The accepted domain boundary is one Owner, one Private instance, and groups managed by that Owner. Telegram group administrators remain distinct actors for group-scoped commands.
- Existing uncommitted ADR 0001 and glossary drafts should be completed and committed with the documentation stage; they must not be discarded when implementation begins.
- The implementation should preserve the repository license and legally required attribution while removing upstream promotional and multi-deployer metadata.
- Each staged change must leave tests and the supported runtime green. Dormant PostgreSQL tables may remain temporarily during the first product-pruning stage, but they disappear with the fresh SQLite baseline.
- The host deploy procedure is: snapshot the SQLite volume, pull
latest or a chosen SHA tag, recreate the bot container, and verify /health plus /ping.
Problem Statement
As the Owner of a personal fork, I am maintaining a public-project-sized Telegram group-management bot even though I operate only one Private instance for groups I personally manage. The repository still carries many unused product domains, PostgreSQL and Redis services, public distribution targets, several monitoring systems, and duplicated CI/release paths. This creates unnecessary dependency updates, schema and cache coordination, background workers, deployment configuration, test cost, and operational failure modes.
The fork needs a deliberately smaller product contract: retain only the Required workflows selected by the Owner, make that contract explicit, and shape the application, state model, container, automation, and documentation around one operator and one production instance.
Solution
Reduce the bot to the explicitly retained commands and automatic behaviors, deleting every other feature rather than merely hiding it. Replace PostgreSQL and Redis with an embedded SQLite database plus in-process ephemeral state. Keep both Telegram polling and webhook delivery, but reduce observability to structured logs and health checks. Deploy one
linux/amd64container from GHCR to one Docker host, with compact verification and publishing workflows instead of the inherited release matrix.Deliver the reduction as staged, independently green changes: contract and feature reduction; SQLite and in-memory state; runtime and container simplification; then CI/CD and documentation cleanup. Production starts with a fresh SQLite volume. Existing PostgreSQL data and old bot exports are not migrated.
User Stories
/banto accept an optional duration, so that one command handles both permanent and temporary bans./unban, so that I can reverse either kind of ban explicitly./kick, so that I can remove a member without preventing re-entry./kickme, so that I can ask the bot to remove me from the group./muteto accept an optional duration, so that one command handles permanent and temporary restrictions./unmute, so that I can restore chat permissions explicitly./warnand warning history, so that repeated behavior can be tracked before automatic enforcement./warns, so that warning counts and reasons remain visible./rmwarnand/resetwarns, so that one or all warnings for a member can be corrected./resetallwarns, so that a group-wide warning reset remains possible after confirmation./setwarnlimit, so that each group can choose its warning threshold./warningsto explain the threshold and fixed policy, so that enforcement is auditable./purge, so that I can delete a contiguous message range without maintaining several purge variants./approvaland/approved, so that exemptions can be inspected and audited.#name./report, so that I can notify administrators without@admininterception or report-setting state./connect, so that I can run supported group commands remotely./connectionand/disconnect, so that the current remote context is visible and can be ended explicitly./stats,/chatinfo,/chatlist, and/leavechat, so that I can inspect and control the Private instance without developer or sudo role delegation./stat,/id,/tell,/ping, and/info, so that the selected utility workflows remain available./start, interactive/help, module help links, and/formatting, so that the reduced product is discoverable without inherited promotional pages./health, so that the container remains operable without tracing, metrics, profiling, or remediation systems.MESSAGE_DUMP.linux/amd64Alpine image with a writable/datavolume, so that production deployment has one supported container path.maincommits published to GHCR aslatestand an immutable short-SHA tag, so that deployment and rollback remain simple.--version, so that a running container can be tied to source without manual version bumps.Implementation Decisions
/ban,/unban,/kick, and/kickme./ban <target> [duration] [reason]recognizesNm,Nh,Nd, andNw; omission means permanent. Remove silent/delete variants,/tban,/dkick,/restrict,/unrestrict, and moderation undo callbacks./muteand/unmute, using the same optional-duration contract. Remove/smute,/tmute, and/dmute./warn,/warns,/rmwarn,/resetwarns,/resetallwarns,/setwarnlimit, and a simplified/warnings. Remove warning aliases and/setwarnmode. Crossing the threshold applies a 72-hour ban./purge. Remove single-delete and two-step range commands and state./approve,/unapprove,/approval, and/approved. Remove bulk clearing and greeting-driven auto-approval./setflood,/setfloodmode,/delflood, and/flood./antiraid,/raidtime,/raidactiontime, and/autoantiraid./blacklists,/addblacklist,/rmblacklist,/blacklistaction, and/rmallbl; delete their duplicate aliases./addfilter,/rmfilter,/filters, text/media replies, the message watcher, and consume-once overwrite confirmation. Remove aliases and bulk clear./welcome,/setwelcome,/cleanwelcome, and/cleanservice. Remove goodbye state and handlers,/resetwelcome, and/autoapprove./addnote,/clear,/rmnote,/notes,/clearall,#name, media/formatting, and overwrite confirmation. Remove/save,/saved,/get, private-note settings, and all note deep links./addreaction,/removereaction,/reactions, and the watcher. Remove bulk reset and help-only callbacks./reportnotification path. Remove@adminmatching, persistence, configuration, disable checks, and resolution callbacks./connect,/disconnect,/connection, and its control keyboard. Any current group administrator may connect. Remove/reconnect, connection history,/allowconnect, authorization settings, and connection deep links./export,/import, and/reset, but its versioned format and transactional adapters include only antiflood, antiraid, approvals, blacklists, connections, filters, welcome, notes, reactions, and warnings. Old export compatibility is not required./stat,/id,/tell,/ping, and/info. Owner operations retain/stats,/chatinfo,/chatlist, and/leavechat.OWNER_ID; group actions continue to use Telegram administrator permissions./start, interactive/help, retained-module callbacks/deep links, and/formatting. Remove Donate, About, setup/onboarding, upstream promotion, and obsolete module metadata./data/alita.db. Enable foreign keys, WAL, a busy timeout, and a conservative connection limit suitable for SQLite's single-writer model./statsand/chatlistderive active/inactive status from persistedlast_activityat request time./healthendpoint that checks process and SQLite readiness. Keep the CLI health probe for the container health check.BOT_TOKENandOWNER_ID; optionalSQLITE_PATH,HTTP_PORT,LOG_LEVEL,MESSAGE_DUMP, andUSE_WEBHOOKS; and conditionalWEBHOOK_DOMAINplusWEBHOOK_SECRETin webhook mode. Defaults are internal rather than environment-tunable.dev; CI injects the short commit SHA. Remove source version constants, bump scripts, and semantic-release coupling.linux/amd64, running as a non-root user with/datawritable. Embed migrations in the binary. Remove debug, PR, and GoReleaser image definitions.mainpublishesghcr.io/fullstacksjs/alita-bot:latestandghcr.io/fullstacksjs/alita-bot:<short-sha>. Deployment is a manual pull and restart on the Docker host.Testing Decisions
/datavolume, verify/health, then restart it and confirm SQLite persistence.Out of Scope
database/sql.Further Notes
latestor a chosen SHA tag, recreate the bot container, and verify/healthplus/ping.