2026-09-04 19:30:54 +02:00
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
Implement the avatar and a preflight dry run
The avatar is now applied rather than deferred: BotFather's /setuserpic is a
conversation in which you send a photo, so the file is uploaded and sent as
a message. It is content addressed -- replacing the file is what triggers an
update, and the digest is recorded only after BotFather confirms, so a failed
upload retries rather than being remembered as done. The image is validated
before the conversation starts, because an image rejected halfway leaves the
bot registered without a picture.
Adds `provision preflight`: spec, avatar, OpenBao reachability, credentials,
session presence, salt and the resulting plan, checked in one run that writes
nothing and never contacts Telegram. Every failure it reports is one that
would otherwise surface after a phone number had been spent.
Two bugs it found immediately. The avatar path is documented as repo-relative
but resolved against the spec's own directory, so the real campaign spec
failed to find its own asset. And the OpenBao error named both variables when
only one was missing, sending the reader to check the one already set.
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
2026-09-04 22:12:56 +02:00
|
|
|
|
## Check everything first
|
|
|
|
|
|
|
|
|
|
|
|
Before spending a phone number, run the dry run. It exercises the spec, the
|
|
|
|
|
|
avatar, the OpenBao wiring, the credentials and the resulting plan, and it
|
|
|
|
|
|
neither writes anything nor contacts Telegram:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
go run ./cmd/provision preflight --spec ../pr-hall-of-helix/presence/telegram.yaml
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Every failure it reports is one that would otherwise have surfaced after an
|
|
|
|
|
|
account was registered or a BotFather conversation was under way. Run it again
|
|
|
|
|
|
after each step below; it is cheap and it is the only check that costs nothing.
|
|
|
|
|
|
|
2026-09-04 19:30:54 +02:00
|
|
|
|
## 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
|
2026-09-04 22:02:56 +02:00
|
|
|
|
- [ ] Access to OpenBao (`bao.coulomb.social`) with write on the campaign's subtree
|
2026-09-04 19:30:54 +02:00
|
|
|
|
- [ ] 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.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
## 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
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-04 22:32:09 +02:00
|
|
|
|
Those are the **development defaults**. The fleet convention for an API-key
|
|
|
|
|
|
bundle is `platform/workloads/<domain>/<workload>/<bundle>`, owned by
|
|
|
|
|
|
railiance-platform; ask ops-warden for the current shape and override it:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
export BAO_MOUNT=platform
|
|
|
|
|
|
export FLUID_BAO_PREFIX=workloads/infotech/fluid-telegram
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
Authenticate the way ops-warden routes you, which is OIDC through key-cape
|
|
|
|
|
|
rather than a plain token login:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
warden access 'openbao token for reading a kv secret' # shows auth, path, policy
|
|
|
|
|
|
bao login -method=oidc role=<domain>
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`bao token lookup` succeeding is the check that the login actually took.
|
2026-09-04 22:02:56 +02:00
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
2026-09-04 19:30:54 +02:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
|
|
|
|
|
|
|
1. Install Telegram on the device and register with the operator phone number.
|
|
|
|
|
|
2. Set a display name that reads as an operator account, not a person.
|
2026-09-04 22:02:56 +02:00
|
|
|
|
3. 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.
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
**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.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
**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.
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
|
|
|
|
|
|
|
1. Go to <https://my.telegram.org>, log in with the operator number.
|
|
|
|
|
|
2. **API development tools** → create an application.
|
|
|
|
|
|
- App title: `helixforge-provisioner`
|
|
|
|
|
|
- Platform: Other
|
|
|
|
|
|
3. Copy `api_id` and `api_hash`.
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
**Record:** both, in OpenBao. This is the one secret you write by hand, and
|
|
|
|
|
|
everything downstream reads it:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
bao kv get secret/fluid-telegram/hall-of-helix/telegram/operator-app
|
|
|
|
|
|
```
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-04 22:02:56 +02:00
|
|
|
|
go run ./cmd/provision session bootstrap --campaign hall-of-helix
|
2026-09-04 19:30:54 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
It prompts for the phone number, the login code, and the 2FA password, then
|
|
|
|
|
|
writes the session string to OpenBao at
|
2026-09-04 22:02:56 +02:00
|
|
|
|
`secret/fluid-telegram/hall-of-helix/telegram/operator-session`.
|
2026-09-04 19:30:54 +02:00
|
|
|
|
|
|
|
|
|
|
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:**
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-04 22:02:56 +02:00
|
|
|
|
go run ./cmd/provision session check --campaign hall-of-helix # prints the account, no secrets
|
2026-09-04 19:30:54 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## Step 4 — Hand over to the provisioner
|
|
|
|
|
|
|
|
|
|
|
|
From here nothing is manual. The remaining work is `FT-WP-0002` T03–T06:
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
2026-09-04 22:02:56 +02:00
|
|
|
|
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
|
2026-09-04 19:30:54 +02:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-09-04 22:02:56 +02:00
|
|
|
|
`apply` registers the bot through BotFather, sets its name, about text and
|
|
|
|
|
|
description, creates the test channel, adds the bot as administrator with
|
2026-09-04 19:30:54 +02:00
|
|
|
|
`post_messages` only, generates the redaction salt if it is absent, and writes
|
|
|
|
|
|
`presence/resolved/hall-of-helix.yaml`.
|
|
|
|
|
|
|
Implement the avatar and a preflight dry run
The avatar is now applied rather than deferred: BotFather's /setuserpic is a
conversation in which you send a photo, so the file is uploaded and sent as
a message. It is content addressed -- replacing the file is what triggers an
update, and the digest is recorded only after BotFather confirms, so a failed
upload retries rather than being remembered as done. The image is validated
before the conversation starts, because an image rejected halfway leaves the
bot registered without a picture.
Adds `provision preflight`: spec, avatar, OpenBao reachability, credentials,
session presence, salt and the resulting plan, checked in one run that writes
nothing and never contacts Telegram. Every failure it reports is one that
would otherwise surface after a phone number had been spent.
Two bugs it found immediately. The avatar path is documented as repo-relative
but resolved against the spec's own directory, so the real campaign spec
failed to find its own asset. And the OpenBao error named both variables when
only one was missing, sending the reader to check the one already set.
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
2026-09-04 22:12:56 +02:00
|
|
|
|
One thing it will *not* do on this first run, by design and reported in the
|
|
|
|
|
|
plan: the **public channel is held** until a rendering has been checked in the
|
|
|
|
|
|
test channel.
|
2026-09-04 22:02:56 +02:00
|
|
|
|
|
2026-09-04 19:30:54 +02:00
|
|
|
|
**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.
|