Two things that would have gone wrong on the first live run. The plan promised "create bot.avatar" and apply had no avatar code at all: /setuserpic needs a photo upload that is not implemented. A plan that promises an action apply silently skips is worse than one that admits the gap, because it makes every other line less trustworthy. The avatar is now deferred, with instructions for setting it by hand. The runbook wrote secrets to "<interface-path>/telegram/...", which is not what the code computes. An operator following it would have put api_id and api_hash somewhere the tool never looks, and found out at the first connection attempt. Both documents now carry the real paths -- <mount>/fluid-telegram/<campaign>/telegram/<key> -- with a copy-pasteable bao kv put for the one secret written by hand, and a verify step before the session is minted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0172sgCZEEDJcnQmr4SGDvKa Assistant: claude-code Assistant-Model: opus Assistant-Process: 1361245@bnt-lap001 Assistant-Session: b3b428ef-f3e6-4688-b091-01f71461d66a
7.5 KiB
Operator runbook — seeding platform access
Some platform access cannot be provisioned from a specification. Telegram will
not let software create the first credential: an account needs a phone and a
device, and api_id / api_hash come from a web form behind a login. No API
exists for either, and none is coming, because their purpose is to establish that
a person is on the other end.
This runbook is what we do instead. It is not a lowering of the bar. A manual step is acceptable when it is guided, repeatable, and bounded — and unacceptable when it is merely undocumented. The test each step below has to pass is that an operator who has never done it can follow it once, get the same result as anyone else, and hand the outcome to a tool that takes over from there.
The rule this runbook operates under
Automate everything that has an interface. Where a step exists specifically to prove a human is present, guide it — and record why it resisted automation, so the judgement can be revisited rather than inherited.
Every manual step below carries a Why not automated line. That line is the thing to re-read when the platform changes. Some of them will stop being true; the sooner that is noticed, the smaller the runbook gets.
There is a real distinction here worth keeping. A KYC identity check resists automation by design — automating it would defeat what it is for, and a component weak enough to allow it is a defect, not an opportunity. A web form that mints an API key resists automation only incidentally, because nobody built the endpoint. The first stays manual on principle. The second is a candidate the moment anything changes.
Telegram's steps are all the second kind. None of them is a fraud control; they are just interfaces that were never built.
Before you start
Have these ready. Stopping halfway through to find one is how the session becomes irreproducible.
- A phone number that can receive SMS, not a personal one
- A device with Telegram installed, to complete the first login
- Access to OpenBao (
bao.coulomb.social) with write on the campaign's subtree - The campaign's presence spec merged (
pr-hall-of-helix/presence/telegram.yaml) - The avatar asset committed and referenced by the spec
Roughly 20 minutes, once.
Where the secrets go
The provisioner computes these paths; they are not a convention you may vary.
For campaign hall-of-helix on the default secret mount:
secret/fluid-telegram/hall-of-helix/telegram/operator-app api_id, api_hash
secret/fluid-telegram/hall-of-helix/telegram/operator-session written by the tool
secret/fluid-telegram/hall-of-helix/telegram/bot-token written by the tool
secret/fluid-telegram/hall-of-helix/telegram/redaction-salt written by the tool, once
Set these before any command below. BAO_MOUNT defaults to secret.
export BAO_ADDR=https://bao.coulomb.social
export BAO_TOKEN=<your token> # not committed, not echoed
Only the first path is written by hand. The rest are the tool's, and writing one yourself will be overwritten or -- for the salt -- silently honoured forever.
Step 1 — Register the operator account
Why not automated: Telegram requires a phone number and an SMS round trip. There is no registration API at any tier. This is the step whose whole purpose is to prove a person is present.
- Install Telegram on the device and register with the operator phone number.
- Set a display name that reads as an operator account, not a person.
- Enable two-factor authentication and store the password somewhere you can retrieve it. The tool prompts for it during Step 3 and does not read it from OpenBao, so this is for you, not for the provisioner.
Do not use a personal account. If it is rate-limited or restricted — which automation can cause — the loss should be a throwaway account, not someone's messages.
Record: the phone number where your team can find it. The tool prompts for it rather than reading it, so it is not one of the paths above.
Step 2 — Obtain api_id and api_hash
Why not automated: issued by a web form at my.telegram.org behind a login code. No API. This one is incidental rather than principled — if Telegram ever exposes it, Step 2 disappears.
- Go to https://my.telegram.org, log in with the operator number.
- API development tools → create an application.
- App title:
helixforge-provisioner - Platform: Other
- App title:
- Copy
api_idandapi_hash.
Record: both, in OpenBao. This is the one secret you write by hand, and everything downstream reads it:
bao kv put secret/fluid-telegram/hall-of-helix/telegram/operator-app \
api_id=<the number> api_hash=<the hash>
Verify the tool can see it before going on:
bao kv get secret/fluid-telegram/hall-of-helix/telegram/operator-app
They are shown once per application. Losing them means creating another.
Step 3 — Mint the MTProto session
Why not automated: the login code arrives out of band, on the device. The command is interactive by necessity — but only this command, and only once.
go run ./cmd/provision session bootstrap --campaign hall-of-helix
It prompts for the phone number, the login code, and the 2FA password, then
writes the session string to OpenBao at
secret/fluid-telegram/hall-of-helix/telegram/operator-session.
The session string is a full-account credential — it can do anything the account can. It never leaves OpenBao, is never given to the adapter, and is used only by the provisioner. Treat losing it as losing the account.
Verify:
go run ./cmd/provision session check --campaign hall-of-helix # prints the account, no secrets
Step 4 — Hand over to the provisioner
From here nothing is manual. The remaining work is FT-WP-0002 T03–T06:
go run ./cmd/provision plan --spec ../pr-hall-of-helix/presence/telegram.yaml # read the diff
go run ./cmd/provision apply --spec ../pr-hall-of-helix/presence/telegram.yaml # execute it
apply registers the bot through BotFather, sets its name, about text and
description, creates the test channel, adds the bot as administrator with
post_messages only, generates the redaction salt if it is absent, and writes
presence/resolved/hall-of-helix.yaml.
Two things it will not do on this first run, both by design and both reported
in the plan. The public channel is held until a rendering has been checked in
the test channel. The avatar is deferred: /setuserpic needs a photo upload
that is not implemented, so set it by hand in @BotFather, or wait.
Read the plan before approving it. That is the human judgement this design keeps — not clicking through BotFather, but deciding whether the diff is what was intended.
Re-running this
Steps 1–2 are once per operator account, ever. Step 3 repeats only if the session is revoked — by a password change, an explicit sign-out, or Telegram invalidating it. When that happens, re-run Step 3 alone; nothing else is affected, because resolved state and the bot token do not depend on the session that created them.
Step 4 is safe to repeat at any time. It is a converging reconciler, and a second apply over an unchanged spec does nothing.
When a step stops being necessary
Re-read the Why not automated lines whenever Telegram changes its API surface, and delete the step if the reason has expired. A runbook nobody prunes becomes a description of how things used to work, and the manual steps in it start looking like requirements rather than gaps.