feat(integrations): share one setup flow; require and resolve the Telegram chat id#4170
Conversation
Greptile code reviewThis repo uses Greptile for automated review. Before merge, aim for Confidence Score: 5/5 with zero unresolved review threads — see CONTRIBUTING.md. Run a review — add a PR comment with: Give it ~5-10 minutes (sometimes longer) for results, then fix feedback and re-trigger until you reach Confidence Score: 5/5. Optional: automate with the greploop skill. |
Greptile SummaryThis PR introduces a declarative
Confidence Score: 5/5Safe to merge. The three-tier write ordering is correct, the exception handling in The central No files require special attention. The only finding is a documentation style nit in Important Files Changed
Sequence DiagramsequenceDiagram
participant Surface as CLI / Wizard / Agent
participant Flow as integrations/setup_flow.py
participant Verifier as telegram/verifier.py
participant Lookup as telegram/chat_lookup.py
participant Env as config/env_file (keyring + .env)
participant Store as integrations/store.py
Surface->>Flow: "apply_setup(spec, {bot_token, chat_ref})"
Flow->>Flow: _collect_credentials() — required field check
Flow->>Verifier: "verify_telegram("setup", {bot_token, chat_ref})"
Verifier-->>Flow: "{status: "passed", detail: "Connected to @bot"}"
Flow->>Lookup: "resolve_chat_id(bot_token, "@acme_alerts")"
Lookup-->>Flow: ("-1001234567890", "Acme Alerts (channel)", "")
Flow->>Env: sync_env_secret("TELEGRAM_BOT_TOKEN", token)
Flow->>Env: "sync_env_values({"TELEGRAM_DEFAULT_CHAT_ID": "-100..."})"
Env-->>Flow: .env path
Flow->>Store: "upsert_integration("telegram", {credentials: ...})"
Flow-->>Surface: "SetupOutcome(ok=True, detail=..., env_path=...)"
Reviews (4): Last reviewed commit: "docs(agents): write docs/ for users, not..." | Re-trigger Greptile |
…egram chat id Every setup surface persisted credentials its own way. `opensre onboard` wrote the integration store, the keyring, and `.env`; `opensre integrations setup` wrote the store alone. Runtime hides the difference by resolving the store first, so it only surfaces in the deploy preflight, which reads env vars — you could configure Telegram, verify it, send messages, and still have `make deploy` report TELEGRAM_BOT_TOKEN as missing. `integrations/setup_flow.py` now owns validate -> verify -> resolve -> persist for every tier, driven by a declarative `IntegrationSetupSpec`. Telegram is migrated onto it as the reference; the remaining 44 handlers are tracked in a follow-up issue. Two behavior changes come with the Telegram spec: * the chat id is required. A bot token alone passes `getMe` but every delivery path raises without a chat id, so accepting a token-only setup produced an integration that looked healthy and failed at the first alert. * the chat id is resolved via `getChat` rather than trusted. Users may supply a public channel's `@name` — far easier to obtain than a numeric id — and it is stored as the numeric id, which survives a channel rename. A chat the bot cannot reach now fails setup instead of failing silently at delivery time. The wizard's httpx copy of the `getMe` probe is deleted; the registered verifier is the single prober. Its test coverage, including the token-redaction case, moves to tests/integrations/telegram/test_verifier.py.
* Persist env/keyring before the store. Previously the store was written
first, so a failing `.env` write (PermissionError from write_env_lines on an
unwritable file) left exactly the store-only state this module exists to
prevent. With the order reversed, an env failure persists nothing at all, and
a store failure still leaves a working integration because credential
resolution falls back to the environment. The env write is also caught and
reported as a failed SetupOutcome instead of propagating to the callers.
* Restore fail-fast on a blank required field. Collecting every prompt before
validating meant a user who skipped the token was still asked for the chat id
before seeing the error. `_run_spec_setup` now checks each field as it is
answered. It is spec-driven, so the remaining integrations get the same
behavior when they migrate.
* Narrow `outcome.env_path` before `str()` in the wizard, so a future change
that stopped setting it would fail loudly rather than returning "None".
Also drops `SetupOutcome.saved`, which vulture flagged: it was exactly `ok`.
`SetupField.prompt` now carries the question text ("Default chat ID or
@channelname") separately from `label`, which stays terse for error messages,
and both surfaces read the prompt from the spec instead of hardcoding it.
d4bc3d8 to
b847b45
Compare
|
@greptile review |
…low (draft — blocked on Tracer-Cloud#4170) Add integrations/slack/setup.py with a declarative SLACK_SETUP_SPEC adapted to the IntegrationSetupSpec API from PR Tracer-Cloud#4170 (muddlebee). Consolidate the Slack verifier docstring to document it as the single source of truth for credential validation. This PR is blocked on Tracer-Cloud#4170 which introduces integrations/setup_flow.py. Once Tracer-Cloud#4170 lands, follow-up changes will migrate _setup_slack() in integrations/cli.py and _configure_slack() in chat_notifications.py onto apply_setup(SLACK_SETUP_SPEC, ...). AI-usage disclosure: Code was written and reviewed with AI assistance (opencode).
``_persist_env`` skipped blank non-secret fields, so clearing one left the old value in ``.env`` while the store recorded ``None``. Credential resolution falls back to the environment when the store is empty, so the value the user had just cleared kept resolving. Blank secrets were already cleared from the keyring — the two halves disagreed. Blank values are now written through, making ``KEY=`` in ``.env``, which loads as an empty string and reads as unset everywhere. Not reachable for Telegram, where both fields are required, but it is the shape an integration with an optional non-secret field would hit during the migration tracked in #4168. Also tells the user, up front, that the wizard's Telegram prompts are both mandatory and that Ctrl+C skips the integration without losing the rest of onboarding. Making the chat id required removed the previous "press Enter to skip" escape, and a bare "Required." did not say what to do instead.
CodeQL (py/import-and-import-from) flagged six new test modules for importing the same module with both ``import x`` and ``from x import y``. Each keeps the module alias — the monkeypatch seam these tests rely on — and drops the from-import, referencing symbols through the alias instead. No behavior change; the alias was already how every patched attribute was reached.
|
@greptile review |
The setup notes described getMe/getChat, the three credential tiers, and the deploy preflight — none of which a user configuring a bot acts on. Kept only what changes what they do: both answers are required, a public channel's @name works instead of hunting a numeric id, and the bot still needs Post Messages to actually post.
Adds a Docs section under Code Style. The rule is the test 'does this sentence change what the reader does?' — with the concrete cuts (vendor API endpoints, internal function names, which credential tier a value lands in, the bug a change fixed) and the keeps (required vs optional, shortcuts, invisible gotchas, exact commands). Prompted by the Telegram setup page picking up getMe/getChat, the three-tier write, and the deploy preflight — all true, none of it actionable by someone setting up a bot.
One test — does this sentence change what the reader does? — plus what to cut and what to keep. Prompted by the Telegram setup page picking up getMe/getChat, the three-tier write, and the deploy preflight: all true, none of it actionable by someone setting up a bot.
Devesh36
left a comment
There was a problem hiding this comment.
quite a good approach for this ! Nice work 👍
|
Nice consolidation, this is great. |
|
demo updated |
|
🌮 @muddlebee's PR: showed up unannounced, improved everything, left zero bugs. Just like a perfect taco. 🌮 👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome. |

Stacked on #4167 — base is
refactor/shared-integration-setup-flow, notmain. Review #4167 first; this PR's own diff is the last commit.Relates to #4071. Follow-ups filed as #4168 (remaining 44 integrations) and #4169 (agent-driven setup).
Describe the changes you have made in this PR -
#4167 moved the
.env/keyring writer down toconfig/sointegrations/could reach it. Nothing used it yet — that PR changed no behavior. This one connects it.The problem. Setting up an integration happens on three surfaces, and they persisted credentials differently:
.envopensre onboardopensre integrations setup telegramconfigure_telegramaction tool (proposed in #4071)Runtime hides this —
integrations/telegram/credentials.pyresolves the store first — so the CLI path looks fine right up until deploy, whereplatform/deployment/ecr_deploy/prep.py:75checks the env var:Configure Telegram with the CLI, verify it, send a message — then watch
make deploytell youTELEGRAM_BOT_TOKENis missing. Configure the same integration withonboardand deploy succeeds. This is not Telegram-specific: 0 of the 45 setup handlers inintegrations/cli.pywrite the keyring or.env.The change.
integrations/setup_flow.pyowns the whole lifecycle once — validate required fields → verify → resolve → persist to every tier — driven by a declarative spec:Callers keep only their own value collection.
_setup_telegramand_configure_telegramboth shrink to a prompt pair plus oneapply_setup(...)call, and neither decides where anything is stored —is_sensitive_env_key()routes by variable name, and the split is enforced (sync_env_valuesrefuses a secret,sync_env_secretrefuses a non-secret).Two deliberate behavior changes, both Telegram-specific:
The chat id is now required. A bot token alone passes
getMe, so setup reported success — butload_credentials_from_env()raises without a chat id, so the watchdog, Hermes sinks, and the send-message tool all fail. The old wizard printed a warning and saved anyway; that just moved the failure to the first real alert, hours later, far from the setup that caused it.The chat id is resolved, not trusted.
getChataccepts a numeric id or an@username, so users can supply a public channel's@name— much easier to obtain than digging a numeric id out ofgetUpdates— and it is stored as the numeric id, which survives a channel rename. More importantly it answers the questiongetMecannot: can this bot see that chat? The most common Telegram misconfiguration is forgetting to add the bot to the channel, and that now fails at setup.One limit is documented rather than hidden:
getChatproves reachability, not posting rights. A channel bot still needs the Post Messages admin permission, and only an actual send would prove it.Deleted:
validate_telegram_bot, the wizard's httpx copy of thegetMeprobe. The registered verifier inintegrations/telegram/verifier.pyis now the single prober; the deleted copy's test coverage — including its token-redaction case — moved totests/integrations/telegram/test_verifier.pyrather than being dropped.Demo/Screenshot for feature changes and bug fixes -
Code Understanding and AI Usage
Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?
If you used AI assistance:
Explain your implementation approach:
Both Telegram setup paths had zero test coverage before #4167, so that PR characterized them first — prompts, validate-before-persist ordering, the wizard's retry loop, the exact credential shape, and the tiers each path wrote. Those tests are what made this change safe to make, and they are updated here only where behavior changed on purpose (chat id required, CLI path now writing all three tiers). Each such edit is called out in the test module docstrings, so a reviewer can tell an intended change from a regression.
The design question was where the "which tier does this value go to" decision lives. Three options:
upsert_integrationmirror to the keyring and.envitself. Tempting, because it would fix all 45 handlers in one change. It does not work: the store key isbot_tokenwhile the env var isTELEGRAM_BOT_TOKEN, so a per-integration name mapping is required regardless. That mapping is the spec, so there is no way to avoid writing one per integration.verifyis wired onto the spec as a direct function reference rather than looked up in the verifier registry. The caller already knows which integration it is configuring, and an explicit reference keeps the flow independent of import-time registration ordering — which also means tests can swap it withdataclasses.replaceand still exercise the real field definitions instead of a stand-in spec.The
resolvehook exists because verification and normalization are genuinely different steps:verifyanswers "do these credentials work" and returns a status, while Telegram additionally needs to rewrite what the user typed. Folding that into the verifier would have changed theVerifierFncontract for all 45 integrations to serve one.Not in this PR: the other 44 integrations (#4168) and the agent-driven setup tool (#4169). #4071 rebases onto this and shrinks to a thin adapter plus the execution gate it is currently missing.
Checklist before requesting a review