Add the operator seeding runbook and enforce the presence schema
Loosen the all-or-nothing stance on provisioning: where a platform has no API to seed access, a guided runbook is the right answer. docs/seeding-runbook.md covers the three Telegram steps that cannot be automated, and each carries a "why not automated" line so the judgement can be revisited rather than inherited. Telegram's steps resist automation incidentally -- nobody built the endpoints -- unlike a control such as KYC, which resists by design and where a weak component would be a defect rather than an opening. Fix the schema's $id, which was a relative path and broke $ref resolution in ordinary validators, and add the visibility/username constraint the field descriptions already claimed. Both specs now validate, and the rejections are tested. 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
This commit is contained in:
parent
d26c4619b8
commit
f06be1f949
4 changed files with 211 additions and 17 deletions
|
|
@ -28,8 +28,20 @@ creation, visibility, administrator rights, the redaction salt — is declared a
|
|||
reconciled.
|
||||
|
||||
That is the honest boundary. This is not "no human steps"; it is **one bounded
|
||||
bootstrap, then a declared surface that converges.** The steps that remain are
|
||||
the ones Telegram places outside any API on purpose.
|
||||
bootstrap, then a declared surface that converges.**
|
||||
|
||||
Those steps are not left to improvisation. `docs/seeding-runbook.md` guides them:
|
||||
what to have ready, what to do, what to record and where, and — for each one — why
|
||||
it could not be automated. A manual step is acceptable when it is guided,
|
||||
repeatable and bounded, and unacceptable when it is merely undocumented.
|
||||
|
||||
Worth distinguishing two reasons a step resists automation, because they age
|
||||
differently. A KYC check resists *by design*; automating it would defeat its
|
||||
purpose, and a component weak enough to permit it is a defect rather than an
|
||||
opportunity. A web form that mints an API key resists only *incidentally*,
|
||||
because nobody built the endpoint. All three Telegram steps are the second kind —
|
||||
none is a fraud control — so each is a candidate for removal the moment the
|
||||
platform changes.
|
||||
|
||||
## Where the specification lives
|
||||
|
||||
|
|
|
|||
149
docs/seeding-runbook.md
Normal file
149
docs/seeding-runbook.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# 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 interface's path
|
||||
- [ ] 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.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
3. Enable two-factor authentication and put the password in OpenBao at
|
||||
`<interface-path>/telegram/operator-2fa` immediately.
|
||||
|
||||
**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, in OpenBao at `<interface-path>/telegram/operator-phone`.
|
||||
|
||||
---
|
||||
|
||||
## 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`.
|
||||
|
||||
**Record:** both, in OpenBao at `<interface-path>/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.
|
||||
|
||||
```bash
|
||||
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
|
||||
`<interface-path>/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:**
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
provision plan --spec ../pr-hall-of-helix/presence/telegram.yaml # read the diff
|
||||
provision apply --spec ../pr-hall-of-helix/presence/telegram.yaml # execute it
|
||||
```
|
||||
|
||||
`apply` registers the bot through BotFather, sets its name, about text,
|
||||
description and avatar, creates both channels, 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`.
|
||||
|
||||
**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.
|
||||
|
|
@ -4,7 +4,8 @@
|
|||
# This file is the contract it is validated against, and lives here because the
|
||||
# provisioner is here. See docs/provisioning.md.
|
||||
$schema: "https://json-schema.org/draft/2020-12/schema"
|
||||
$id: "fluid-telegram/presence/telegram.schema.yaml"
|
||||
# No $id: this schema is identified by its path in the repo. A relative $id is
|
||||
# not a resolvable URI and breaks "$ref" resolution in ordinary validators.
|
||||
title: "Telegram declared presence"
|
||||
type: object
|
||||
required: [presence]
|
||||
|
|
@ -89,6 +90,21 @@ $defs:
|
|||
type: object
|
||||
required: [title, visibility]
|
||||
additionalProperties: false
|
||||
# A private channel has no username to claim. Declaring one is a mistake
|
||||
# about what is being created, so it fails here rather than being ignored
|
||||
# by apply -- a silently dropped field is how a spec stops describing
|
||||
# what actually exists.
|
||||
allOf:
|
||||
- if:
|
||||
properties: { visibility: { const: private } }
|
||||
required: [visibility]
|
||||
then:
|
||||
not: { required: [username_preference] }
|
||||
- if:
|
||||
properties: { visibility: { const: public } }
|
||||
required: [visibility]
|
||||
then:
|
||||
required: [username_preference]
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ designated operator, and three things stay human — a phone number, the
|
|||
`api_id`/`api_hash` web form, and the login code that mints the session. That is
|
||||
one bounded bootstrap, after which the surface is declared and converges.
|
||||
|
||||
## T01 — Bootstrap the operator account and session
|
||||
## T01 — Write the seeding runbook and bootstrap the session
|
||||
|
||||
```task
|
||||
id: FT-WP-0002-T01
|
||||
|
|
@ -41,36 +41,53 @@ priority: high
|
|||
state_hub_task_id: "039e5358-07c1-5b01-a186-5ece9aab75e4"
|
||||
```
|
||||
|
||||
**The irreducible human step.** Register a dedicated Telegram account for the
|
||||
operator — never a person's own, so that a rate limit costs nothing personal.
|
||||
Obtain `api_id` / `api_hash` from my.telegram.org.
|
||||
**The seeding steps, done by a guided runbook.** `docs/seeding-runbook.md` is
|
||||
the deliverable: register a dedicated operator account (never a person's own, so
|
||||
a rate limit costs nothing personal), obtain `api_id` / `api_hash`, and run
|
||||
`provision session bootstrap` to mint the MTProto session into OpenBao.
|
||||
|
||||
Then `provision session bootstrap` runs the interactive login once and writes the
|
||||
session string to OpenBao. The command is the only interactive one in the tool,
|
||||
and it exists so that the interactive part is bounded and named rather than
|
||||
spread through the process.
|
||||
Manual is acceptable here because it is guided, repeatable and bounded — an
|
||||
operator who has never done it follows it once and gets the same result as
|
||||
anyone else. What makes it acceptable is the structure, not the shortness.
|
||||
|
||||
Nothing else in this workplan is interactive.
|
||||
Each step in the runbook carries a **Why not automated** line, and that is the
|
||||
part with a future. Telegram's three steps resist automation only incidentally —
|
||||
nobody built the endpoints — as opposed to a control like KYC, which resists
|
||||
automation by design and where a component weak enough to allow it would be a
|
||||
defect rather than an opening. So these lines are re-read when the platform
|
||||
changes, and steps are deleted when a reason expires.
|
||||
|
||||
`provision session bootstrap` is the only interactive command in the tool. Nothing
|
||||
else in this workplan is interactive.
|
||||
|
||||
## T02 — Publish the presence schema and the campaign's spec
|
||||
|
||||
```task
|
||||
id: FT-WP-0002-T02
|
||||
status: todo
|
||||
status: progress
|
||||
priority: high
|
||||
state_hub_task_id: "01684264-37a5-5fe8-a491-37686e094428"
|
||||
```
|
||||
|
||||
The schema is in `presence/telegram.schema.yaml` with a worked example beside it.
|
||||
What remains is the real instance at `pr-hall-of-helix/presence/telegram.yaml`,
|
||||
plus the avatar asset.
|
||||
**Largely done.** The schema is in `presence/telegram.schema.yaml` with a worked
|
||||
example beside it, and the real instance is at
|
||||
`pr-hall-of-helix/presence/telegram.yaml`. Both validate.
|
||||
|
||||
The spec is editorial — a bot's name, its description, the channel titles a
|
||||
stranger reads — so it belongs to the campaign. The provisioner takes a spec path
|
||||
as input, which is what keeps this interface reusable by campaigns other than the
|
||||
Hall of Helix.
|
||||
|
||||
Blocked on `pr-hall-of-helix` existing.
|
||||
The schema enforces rather than describes: an `admin_rights` entry other than
|
||||
`post_messages` is rejected, a private channel may not declare a username, a
|
||||
public one must, and unknown fields fail instead of being ignored. A silently
|
||||
dropped field is how a spec stops describing what actually exists.
|
||||
|
||||
**Remaining:** the avatar asset. `presence/assets/helixforge-avatar.png` is
|
||||
referenced but absent, and `apply` will fail on `/setuserpic` until it exists.
|
||||
It is left for a person on purpose — it is the first thing a stranger sees of
|
||||
HelixForge on Telegram, and a generated placeholder would quietly become
|
||||
permanent.
|
||||
|
||||
## T03 — Implement `provision plan`
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue